From ff2c949361360f504c7bc737f21ba6c0186c85a1 Mon Sep 17 00:00:00 2001
From: Joseph Ferano
Date: Thu, 17 Sep 2026 19:03:27 +0700
Subject: [PATCH] The tagged sum is defdata, and the old spelling is an error
by name
Flan's tagged sum has been spelled defunion since it landed, which was
accurate right up until the language wanted C's untagged union as well.
Both cannot be called the same thing, and the tagged one is the one with
an alternative name that says what it is: a case, its fields, and a tag
that steers which case is live is a data type, not a union.
So the form is defdata everywhere -- the parser, the AST, the checker,
both backends, the prelude's Form, the editor's font-locking and imenu,
the docs and every .flan file in the tree. The internal vocabulary moves
with it: Tast.union is Tast.data, uname is dname, the tables the checker
and the emitter keep are datas. Leaving them would have inverted the
words permanently, with surface defunion meaning one thing and
env.unions meaning the other, which is exactly the kind of drift the
comments in those files exist to prevent. What did not move is case,
variant and vfields: a tagged sum still has cases, and it still has one
live at a time.
defunion is not kept as an alias. An alias would compile the day the
untagged form lands and mean the opposite of what it used to -- the same
silent misparse that made defn's return type mandatory, and worse,
because the reader would have no reason to look. The old spelling is a
named refusal instead, parse/defunion-renamed, which says what it is now
called and that the name is reserved for something else. It fires on the
head alone, so (defunion U [A B]) -- which would otherwise have parsed
cleanly as one field A of type B -- is refused with the rest.
---
NEXT.md | 2 +-
bin/main.ml | 2 +-
docs/BUILT.md | 16 +-
emacs/flan-inspect.el | 20 +--
emacs/flan-mode.el | 6 +-
emacs/test-flan-cider.el | 14 +-
lib/ast.ml | 4 +-
lib/check.ml | 202 +++++++++++-----------
lib/dev.ml | 17 +-
lib/emit.ml | 92 +++++-----
lib/expand.ml | 6 +-
lib/load.ml | 9 +-
lib/macro.ml | 2 +-
lib/parse.ml | 26 ++-
lib/prelude.ml | 4 +-
lib/reach.ml | 2 +-
lib/render.ml | 15 +-
lib/session.ml | 26 +--
lib/shim.ml | 12 +-
lib/tast.ml | 19 +-
lib/types.ml | 2 +-
lib/x86.ml | 56 +++---
syntax-sketch.flan | 2 +-
test/programs/{unions.flan => datas.flan} | 4 +-
test/programs/dev-inspect.flan | 2 +-
test/test_acceptance.ml | 118 ++++++-------
test/test_dev.ml | 14 +-
test/test_flan.ml | 22 ++-
test/test_valgrind.ml | 4 +-
web/index.html | 6 +-
30 files changed, 383 insertions(+), 343 deletions(-)
rename test/programs/{unions.flan => datas.flan} (98%)
diff --git a/NEXT.md b/NEXT.md
index e151fd9..cef7c9a 100644
--- a/NEXT.md
+++ b/NEXT.md
@@ -1904,7 +1904,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
What is left for the macro lane, and it is one thing: **`load.ml:312` refuses an imported union outright**, so a
union is file-local. That is not a blocker for `Form` — the prelude is parsed and prepended into the same flat
- namespace before `collect` runs, so a `defunion Form` in `prelude.ml` is an ordinary same-file declaration and
+ namespace before `collect` runs, so a `defdata Form` in `prelude.ml` is an ordinary same-file declaration and
needs no import and no `load.ml` change. Verified by declaring one there and matching it from a program.
**Macros landed on top of this** and needed no `load.ml` change for `Form`, exactly as this said. See
diff --git a/bin/main.ml b/bin/main.ml
index 13ee066..8945a6e 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -39,7 +39,7 @@ let summarise (d : Flan.Ast.decl) =
| Import (a, p) -> Printf.sprintf "import %s %S" a p
| Defalias (n, _) -> Printf.sprintf "defalias %s" n
| Defstruct (n, fs) -> Printf.sprintf "defstruct %s (%d fields)" n (List.length fs)
- | Defunion (n, vs) -> Printf.sprintf "defunion %s (%d cases)" n (List.length vs)
+ | Defdata (n, vs) -> Printf.sprintf "defdata %s (%d cases)" n (List.length vs)
| Defvar (n, _, _) -> Printf.sprintf "defvar %s" n
| Defconst (n, _, _) -> Printf.sprintf "defconst %s" n
| Declare (fn, csym) ->
diff --git a/docs/BUILT.md b/docs/BUILT.md
index 70f538a..3217e32 100644
--- a/docs/BUILT.md
+++ b/docs/BUILT.md
@@ -724,7 +724,7 @@ reason, and a `defmacro` typed at the REPL becomes an ordinary `Ast.Defn` that n
it was rather than half-fixed here, and pinned in `test_session` so that changing it is a decision.
**An expression that expands to a declaration is refused, by name.** `defn`, `defvar`, `defconst`, `defstruct`,
-`defunion`, `defenum`, `defalias`, `defmacro` and `import` are heads `Parse.expr` now rejects — the arm that used to
+`defdata`, `defenum`, `defalias`, `defmacro` and `import` are heads `Parse.expr` now rejects — the arm that used to
say it for `defmacro` alone, generalised. It sits in the head dispatch and not in a walk over what the expander
answered, so it catches a declaration nested anywhere in the expansion for free, catches one **typed** by hand with the
same sentence instead of "unknown name defvar", and cannot drift out of sync with `decl`'s list the way a second copy
@@ -2686,7 +2686,7 @@ are involved, and none are needed" — and monomorphisation is what would buy it
## Unions, and the tag they carry
-`defunion` parsed and its shape was checked long before this; naming the type (`check.ml:312`) and constructing a
+`defdata` parsed and its shape was checked long before this; naming the type (`check.ml:312`) and constructing a
value (`:1075`) were both refused as milestone 6. They are not any more.
### It is closer than the milestone number suggested, and the reason is `Option`
@@ -2739,7 +2739,7 @@ the payload at offset 8, and 40/8 for a struct holding one.
### The surface
```clojure
-(defunion Shape
+(defdata Shape
[Empty
(Dot [x f64 y f64])
(Rect [w i32 h i32])
@@ -2801,13 +2801,13 @@ is a layout change, the same way reordering a struct's fields is.
A union that contains itself by value has no finite size, and `payload_lay`
would recurse forever laying one out rather than failing. It does not get the
-chance: `check_finite` already walked a union's cases, so `(defunion T [Leaf
+chance: `check_finite` already walked a union's cases, so `(defdata T [Leaf
(Node [l T r T])])` is refused with *"T contains itself by value, so it has no
size — go through (Ptr T)"*, and so is a pair of unions that contain each
other. Through a pointer it works, and that is the shape a `Form` has:
```clojure
-(defunion Tree [Leaf (Node [l (Ptr Tree) n i32])])
+(defdata Tree [Leaf (Node [l (Ptr Tree) n i32])])
(defn depth [t (Ptr Tree)] i32
(match (deref t)
@@ -2842,7 +2842,7 @@ enum arm already had, and for the same reason: the name is erased before any bac
An **imported** union is still refused by name at `load.ml:312`, so a union is file-local. That is not a blocker for
the macro expander: the prelude is parsed and prepended into the same flat namespace before `collect` runs, so a
-`defunion Form` in `prelude.ml` is an ordinary same-file declaration needing no import — verified by declaring one
+`defdata Form` in `prelude.ml` is an ordinary same-file declaration needing no import — verified by declaring one
there and matching it from a program. `dev.ml`'s inspector still says "union values are milestone 6" for a stopped
frame's locals, and `shim.ml`'s "a Flan union has no C layout" is now inaccurate as prose though the refusal it guards
is still right: a union has a C layout and still may not cross to C by value, because the shim flattens aggregates.
@@ -3022,7 +3022,7 @@ qualified under the alias the package was imported as. See "A package may declar
### `Form`, and the three numbers
-A macro's parameter and its result are `Form`, so `Form` has to exist on the Flan side: a `defunion` in `prelude.ml`
+A macro's parameter and its result are `Form`, so `Form` has to exist on the Flan side: a `defdata` in `prelude.ml`
mirroring `lib/form.ml`. It mirrors `Form.value` and **not** `Form.t` — there is no `loc` field, deliberately. A macro
cannot invent a source location, so the unmarshaller stamps the **call site's** `Loc.t` onto every node of what a
macro returns. That is the structural answer to "keep the source location of the call site attached to what a macro
@@ -3746,7 +3746,7 @@ the thing it calls. `Prelude.bootstrap` is the hook, a ref rather than a paramet
`Check.program` and it cannot be told.
Only `defn`s are dropped: the functions that survive still mention the prelude's types, and a reduced prelude missing
-them would not check. A `defstruct`, `defunion`, `defalias`, `defenum` or `defvar` therefore stays whatever it names.
+them would not check. A `defstruct`, `defdata`, `defalias`, `defenum` or `defvar` therefore stays whatever it names.
There used to be a sharper reason — `Parse.prelude_types` memoised the prelude's type names for the parser's
return-type guess, and it can be forced for the first time inside a bootstrap build, so a reduced set cached there
would have been wrong for every compile afterwards. That set is gone with the guess; see *The return type is the slot*
diff --git a/emacs/flan-inspect.el b/emacs/flan-inspect.el
index 10a4416..c77d2e1 100644
--- a/emacs/flan-inspect.el
+++ b/emacs/flan-inspect.el
@@ -62,7 +62,7 @@
;; frame it means;
;;
;; the slot root names one frame and one slot, so it is exact, and it
-;; reaches an option's payload and a union case's fields, which have offsets
+;; reaches an option's payload and a data type case's fields, which have offsets
;; but no accessor. It needs a stopped program, it is refused if the
;; frame's body was redefined since it was entered — the same slot
;; fingerprint the listing is refused by — and it cannot root at an
@@ -275,7 +275,7 @@ whatever the value came from."
;; is walking a type, so a field is its name and an element is its number.
;; Two cases need more than the name.
;;
-;; A union's payload sits at an offset that depends on which case the value
+;; A data type's payload sits at an offset that depends on which case the value
;; is in, and only the renderer knows which case it currently is — it wrote
;; `(Union.case {.f …})'. So the type travels with the step and the wire
;; spelling is `Union.case.f'. Guessing the case from a field name two
@@ -686,14 +686,14 @@ root, which is what makes a mixed stack unconstructible."
(unless step (user-error "flan: nothing to inspect on this line"))
(let ((why (flan-inspect-refusal node flan-inspect--root)))
(when why (user-error "flan: %s" why)))
- ;; A union case's field, under an expression root. This is a refusal of
+ ;; A data type case's field, under an expression root. This is a refusal of
;; the *parent* and not of the value at point, which is why it is here and
;; not in `flan-inspect-refusal\=': a struct field that happens to hold a
- ;; union is reached by an ordinary accessor and must stay enterable; it is
- ;; a field *of the union itself* that has no accessor. `(match ...)\=' is
- ;; how a union is opened in the language, and it binds names rather than
+ ;; data type is reached by an ordinary accessor and must stay enterable;
+ ;; it is a field *of the data type itself* that has no accessor. `(match ...)\=' is
+ ;; how a data type is opened in the language, and it binds names rather than
;; producing a value to send, so there is nothing to build here. The
- ;; renderer wrote the head as `Union.case\=', which is the one type spelling
+ ;; renderer wrote the head as `Type.case\=', which is the one type spelling
;; with a dot in it — a package qualifies with a slash.
(let ((ty (plist-get flan-inspect--node :type)))
(when (and (not (eq (car-safe flan-inspect--root) :slot))
@@ -701,14 +701,14 @@ root, which is what makes a mixed stack unconstructible."
(string-match-p "\\." ty))
(user-error
"flan: %s"
- (concat "a union case's field: it is reached by (match ...) in the "
+ (concat "a data type case's field: it is reached by (match ...) in the "
"language, not by an accessor, so there is no expression to "
"send. `i' on a local in the break buffer roots at the frame's "
"slot instead, and that root steps into it by offset"))))
;; The line carries the step that names the field; what the wire needs
;; beyond the name is the type it is a field *of*, and that is this
- ;; buffer's own node — the parent of the one at point. A union's payload
- ;; sits at an offset that depends on the case, so `Union.case\=' has to
+ ;; buffer's own node — the parent of the one at point. A data type's payload
+ ;; sits at an offset that depends on the case, so `Type.case\=' has to
;; travel with the name. An option's payload has no name at all and is
;; the symbol `some\='.
(let* ((step (if (eq (plist-get flan-inspect--node :kind) 'option)
diff --git a/emacs/flan-mode.el b/emacs/flan-mode.el
index 17a2bb4..deb09dd 100644
--- a/emacs/flan-mode.el
+++ b/emacs/flan-mode.el
@@ -89,7 +89,7 @@
:prefix "flan-")
(defconst flan--definers
- '("defn" "defvar" "defconst" "defstruct" "defunion" "defenum" "defalias"
+ '("defn" "defvar" "defconst" "defstruct" "defdata" "defenum" "defalias"
"declare" "import" "package")
"Forms that introduce a top-level name.")
@@ -132,7 +132,7 @@ below — and not again here.")
;; that ignored the column would offer one.
(defvar flan-imenu-generic-expression
`(("Functions" ,(concat "^(defn\\s-+" flan--name-re) 1)
- ("Types" ,(concat "^(def\\(?:struct\\|union\\|enum\\|alias\\)\\s-+"
+ ("Types" ,(concat "^(def\\(?:struct\\|data\\|enum\\|alias\\)\\s-+"
flan--name-re)
1)
("Variables" ,(concat "^(def\\(?:var\\|const\\)\\s-+" flan--name-re) 1)
@@ -434,7 +434,7 @@ decision to `calculate-lisp-indent'."
(flan--count-indent method indent-point last-sexp head-column))
((eq method :defn) (+ lisp-body-indent head-column))
;; No spec. Anything else spelled `def…' is a definition and indents
- ;; like one, which covers `defstruct', `defunion', `defenum', `defvar',
+ ;; like one, which covers `defstruct', `defdata', `defenum', `defvar',
;; `defconst' and `defalias' without naming them.
((and name (string-match-p "\\`def" name))
(+ lisp-body-indent head-column))
diff --git a/emacs/test-flan-cider.el b/emacs/test-flan-cider.el
index 767ef19..f9e912a 100644
--- a/emacs/test-flan-cider.el
+++ b/emacs/test-flan-cider.el
@@ -562,10 +562,10 @@ unwind would send the next one to a daemon that is not there."
(test-flan--check "and the trail says so"
(string-match-p "\\`o \\[frame 0\\]\\.some\n" (buffer-string)))))
-;; A union case's field. The payload sits at an offset that depends on which
+;; A data type case's field. The payload sits at an offset that depends on which
;; case the value is in, and only the renderer knows which it currently holds
;; — it wrote the head `Shape.circle'. So the case travels with the name.
-(test-flan--check "a union field carries its case on the wire"
+(test-flan--check "a data type field carries its case on the wire"
(equal (flan-inspect-wire-step '(:field "r" "Shape.circle"))
"Shape.circle.r"))
(test-flan--check "a struct field does not"
@@ -576,15 +576,15 @@ unwind would send the next one to a daemon that is not there."
;; And the other half of that pair: under an *expression* root there is no
;; accessor to send, so RET refuses there rather than sending `(.at s)' for
-;; the checker to reject. A union's fields are reached by `(match ...)' in
+;; the checker to reject. A data type's fields are reached by `(match ...)' in
;; the language, which binds names rather than producing a value. It is a
;; refusal of the parent, not of the value at point — a struct field that
-;; merely *holds* a union is an ordinary accessor and stays enterable.
+;; merely *holds* a data type is an ordinary accessor and stays enterable.
(let ((buf (test-flan--inspect "s" "(Shape.circle {.at (V {.x 1 .y 2})})")))
(with-current-buffer buf
(goto-char (point-min))
(flan-inspect-next)
- (test-flan--check "an expression root refuses a union case's field"
+ (test-flan--check "an expression root refuses a data type case's field"
(string-match-p
"reached by (match"
(or (test-flan--caught #'flan-inspect-into) "")))))
@@ -600,7 +600,7 @@ unwind would send the next one to a daemon that is not there."
(flan-inspect--show '(:expr "c") nil)
(with-current-buffer " *test-inspect*"
(goto-char (point-min))
- (flan-inspect-next) (flan-inspect-next) ; .s, which holds the union
+ (flan-inspect-next) (flan-inspect-next) ; .s, which holds the data type
(test-flan--check "but a struct field that merely holds one is enterable"
(progn (flan-inspect-into) (equal (car asked) "(.s c)")))))))
@@ -615,7 +615,7 @@ unwind would send the next one to a daemon that is not there."
(goto-char (point-min))
(flan-inspect-next)
(flan-inspect-into)
- (test-flan--check "RET into a union field names the case it is in"
+ (test-flan--check "RET into a data type field names the case it is in"
(equal (plist-get (car test-flan--asked) :path)
'("Shape.circle.at")))))
diff --git a/lib/ast.ml b/lib/ast.ml
index 2edcf93..75473d1 100644
--- a/lib/ast.ml
+++ b/lib/ast.ml
@@ -152,7 +152,7 @@ and decl_kind =
| Import of string * string (* alias, path *)
| Defalias of string * texpr
| Defstruct of string * field list
- | Defunion of string * variant list
+ | Defdata of string * variant list
| Defn of fn
(* No body, so no [defn]: a foreign function, and the string is the C symbol
it is actually called by (plan.org, Types — [declare] is kept only where
@@ -182,7 +182,7 @@ and init = Zeroed | Uninit | Init of expr
drift apart. *)
let declared_name (d : decl) =
match d.d with
- | Defenum (n, _) | Defalias (n, _) | Defstruct (n, _) | Defunion (n, _)
+ | Defenum (n, _) | Defalias (n, _) | Defstruct (n, _) | Defdata (n, _)
| Defvar (n, _, _) | Defconst (n, _, _) -> Some n
| Declare (fn, _) | DeclareC (fn, _) | Defn fn -> Some fn.name
| Package _ | Import _ -> None
diff --git a/lib/check.ml b/lib/check.ml
index 9253654..2df96bb 100644
--- a/lib/check.ml
+++ b/lib/check.ml
@@ -14,7 +14,7 @@
The rule from the two misparse bugs applies here too: *anything not yet
implemented is rejected by name*, never approximated. Milestone 2 is
calc-me.flan and nothing more (plan.org, Build sequence), so [Vec], [Map],
- [Result]/[try], user unions, closures, [dotimes], [defer], generics and
+ [Result]/[try], user data types, closures, [dotimes], [defer], generics and
cross-package imports are all errors with a message that says which
milestone they belong to. *)
@@ -43,17 +43,17 @@ type binding = {
type env = {
structs : (string, Tast.structure) Hashtbl.t;
- unions : (string, Tast.union) Hashtbl.t;
- (* Every union case, twice over: once under its full spelling ["U.C"], which
+ datas : (string, Tast.data) Hashtbl.t;
+ (* Every data type case, twice over: once under its full spelling ["U.C"], which
is how a value of it is written, and once under the bare ["C"], which is
how a [match] arm names it and how a mistake spells a constructor. The
full spelling is a key rather than something split out of a dotted name at
- the use site, because a union's own name can contain a slash (an imported
+ the use site, because a data type's own name can contain a slash (an imported
[rl/U]) and may one day contain a dot; string surgery would own an edge
this does not have to.
The bare entry is deliberately last-writer-wins and is *only* used to say
- "C is a case of U, write (U.C ...)". Two unions may share a case name —
+ "C is a case of U, write (U.C ...)". Two data types may share a case name —
construction is qualified and a pattern resolves against the scrutinee, so
both are unambiguous — and refusing that would be a restriction with no
mechanism behind it. *)
@@ -124,7 +124,7 @@ type env = {
let new_env () = {
structs = Hashtbl.create 16;
- unions = Hashtbl.create 16;
+ datas = Hashtbl.create 16;
cases = Hashtbl.create 32;
aliases = Hashtbl.create 16;
consts = Hashtbl.create 16;
@@ -161,7 +161,7 @@ let declared_note env name =
match Hashtbl.find_opt env.structs name with
| Some s -> List.map (fun (f : Tast.field) -> f.Tast.fname) s.Tast.fields
| None ->
- (match Hashtbl.find_opt env.unions name with
+ (match Hashtbl.find_opt env.datas name with
| Some u -> List.map (fun (c : Tast.variant) -> c.Tast.vname) u.Tast.cases
| None -> [])
in
@@ -282,7 +282,7 @@ type ctx = {
[dead] is the slots whose value has been moved out, with where it went, so
that a second use names the first rather than reporting a type error about
nothing. It is flow-sensitive at an [if]: the two arms are checked from
- the same starting set and the *union* survives the join, so moving in one
+ the same starting set and the *data type* survives the join, so moving in one
arm only is still a move afterwards — and moving in both arms, which is
legal, is not two errors.
@@ -538,7 +538,7 @@ let map_type ?(preds = []) loc (k : Types.t) (v : Types.t) =
elements, a [defvar] with no initialiser are all all-bytes-zero — and a
zeroed function value is a null pointer with a signature on it, which is the
one kind of zero that cannot be used for anything. Every other type's zero
- is a value: 0, false, an empty slice, [None], a union's first case. So these
+ is a value: 0, false, an empty slice, [None], a data type's first case. So these
are refused where they are written rather than left to crash at the call.
A parameter, a return type, a [let] binding and an [(Option (Fn ...))] are
@@ -658,7 +658,7 @@ and near_miss env n =
Types.primitive_names
@ Hashtbl.fold (fun k _ acc -> k :: acc) env.aliases []
@ Hashtbl.fold (fun k _ acc -> k :: acc) env.structs []
- @ Hashtbl.fold (fun k _ acc -> k :: acc) env.unions []
+ @ Hashtbl.fold (fun k _ acc -> k :: acc) env.datas []
@ Hashtbl.fold (fun k _ acc -> k :: acc) env.enums []
in
List.find_opt (fun c -> c <> n && one_edit n c) candidates
@@ -711,12 +711,12 @@ and resolve_name env ~seen loc n =
fail loc "the type alias %s is defined in terms of itself" n
else resolve env ~seen:(n :: seen) (Hashtbl.find env.aliases n)
| _ when Hashtbl.mem env.structs n -> Types.Named n
- (* A union is [Named] exactly as a struct is: one case in [Types.t]
+ (* A data type is [Named] exactly as a struct is: one case in [Types.t]
covers both, and which table the name is in is what tells them apart.
- Keeping them one case is what lets a union be a field, a parameter, a
+ Keeping them one case is what lets a data type be a field, a parameter, a
return type and a slot without a single one of those paths learning
- that unions exist. *)
- | _ when Hashtbl.mem env.unions n -> Types.Named n
+ that data types exist. *)
+ | _ when Hashtbl.mem env.datas n -> Types.Named n
| _ when Hashtbl.mem env.enums n -> Types.Enum n
(* A typo in a primitive is lowercase too, and the type-variable rule
below would otherwise report [f65] as unimplemented generics and send
@@ -1250,16 +1250,16 @@ let rec key_pair env loc (k : Types.t) : Tast.fnref * Tast.fnref =
| t when bytewise_key t ->
Tast.Rtfn "flan_hash_flat", Tast.Rtfn "flan_eq_flat"
| Types.Named n when Hashtbl.mem env.structs n -> struct_key_pair env loc n
- (* A union key would have to hash the tag and then only the bytes the case in
- hand actually uses — the rest of the payload is indeterminate, exactly as
- a struct's padding is, so hashing the blob would make two equal values
+ (* A data type key would have to hash the tag and then only the bytes the
+ case in hand actually uses — the rest of the payload is indeterminate,
+ exactly as a struct's padding is, so hashing the blob would make two equal values
hash differently. That is a per-case walk driven by a switch, which is a
different shape from the field list [struct_key_pair] emits and which
nothing has yet wanted. Refused by name rather than written untested. *)
- | Types.Named n when Hashtbl.mem env.unions n ->
+ | Types.Named n when Hashtbl.mem env.datas n ->
fail loc
- "%s is a union, and a union is not a map key: the payload past the case \
- in hand is indeterminate, so hashing the bytes would make two equal \
+ "%s is a data type, and a data type is not a map key: the payload past \
+ the case in hand is indeterminate, so hashing the bytes would make two equal \
values hash differently. Hashing one needs a per-case walk, which is \
not written — key on the tag, or on a struct holding what you meant" n
| Types.Array (_, e) ->
@@ -1768,19 +1768,19 @@ and var ctx loc ~want name =
same reason: there is nothing to put in the braces. A case that does
have fields is refused here rather than silently zeroed, because ZII
on a constructor would quietly produce a value nobody wrote. *)
- | Some (uname, c) when String.contains name '.' ->
+ | Some (dname, c) when String.contains name '.' ->
if c.Tast.vfields <> [] then
fail loc
"%s has fields, so it needs them — write (%s {.%s ...})"
name name
(List.hd c.Tast.vfields).Tast.fname;
expect loc ~want
- (mk loc (Types.Named uname)
- (Tast.MakeCase (uname, c.Tast.vname, [])))
- | Some (uname, c) ->
+ (mk loc (Types.Named dname)
+ (Tast.MakeCase (dname, c.Tast.vname, [])))
+ | Some (dname, c) ->
fail loc
- "%s is a case of the union %s, and a union value names both — \
- write %s.%s" name uname uname c.Tast.vname
+ "%s is a case of the data type %s, and a data type value names both — \
+ write %s.%s" name dname dname c.Tast.vname
| None ->
(* A bare function name *is* the function. This is a Lisp-1 — one
top-level namespace, enforced, so a defn and a defvar cannot share
@@ -2483,7 +2483,7 @@ and check_if ctx ?(tail = false) ?want loc c t e =
let t = branch ctx (fun () -> in_tail (fun () -> check ctx t)) in
expect loc ~want (mk loc Types.Unit (Tast.If (c, t, unit_at loc)))
| Some e ->
- (* Both arms start from the same dead set and the union survives: moving in
+ (* Both arms start from the same dead set and the data type survives: moving in
one arm only still kills the binding afterwards, and moving in both —
which is legal and common — is not reported twice. A flat set would have
refused [(if c (free v) (free v))] and allowed the use after a one-armed
@@ -2514,31 +2514,31 @@ and check_if ctx ?(tail = false) ?want loc c t e =
mk loc ty (Tast.If (c, t, e))
(* A record-shaped literal: one form for both, because [(Name {.f v})] is the
- same syntax whether [Name] is a struct or a union case, and the two differ
+ same syntax whether [Name] is a struct or a data type case, and the two differ
only in what is built at the end. Deciding here rather than in the parser is
what lets the decision be made against the tables, exactly. *)
and check_struct ctx ~want loc name kvs =
match Hashtbl.find_opt ctx.env.structs name with
| None ->
(match Hashtbl.find_opt ctx.env.cases name with
- (* The full spelling [U.C], which is how a union value is written. Checked
+ (* The full spelling [U.C], which is how a data type value is written. Checked
before the diagnostics below, since the bare-name entry in the same
table is only ever a hint. *)
- | Some (uname, c) when String.contains name '.' ->
- check_case ctx ~want loc uname c kvs
+ | Some (dname, c) when String.contains name '.' ->
+ check_case ctx ~want loc dname c kvs
(* A bare case name. This is the bug NEXT.md listed under "Bugs found and
- not yet fixed": [(A {.x 1})] on a case of a union reported "unknown
+ not yet fixed": [(A {.x 1})] on a case of a data type reported "unknown
struct A", because nothing in [env] could tell a case name from a
misspelling. It can now, so it says what was meant. *)
- | Some (uname, c) ->
+ | Some (dname, c) ->
fail loc
- "%s is a case of the union %s, not a struct — a union value names \
+ "%s is a case of the data type %s, not a struct — a data type value names \
both, as (%s.%s {.field value ...})"
- name uname uname c.Tast.vname
+ name dname dname c.Tast.vname
| None ->
- if Hashtbl.mem ctx.env.unions name then
+ if Hashtbl.mem ctx.env.datas name then
fail loc
- "%s is a union, and a union value names the case as well as the \
+ "%s is a data type, and a data type value names the case as well as the \
type — write (%s.%s {.field value ...}) for one of %s"
name name (first_case_name ctx.env name) (case_list ctx.env name)
else
@@ -2573,25 +2573,25 @@ and check_struct ctx ~want loc name kvs =
in
expect loc ~want (mk loc (Types.Named name) (Tast.Make (name, fields)))
-(* The cases of a union, as written, for a message that has to name them. *)
-and case_list env uname =
- match Hashtbl.find_opt env.unions uname with
+(* The cases of a data type, as written, for a message that has to name them. *)
+and case_list env dname =
+ match Hashtbl.find_opt env.datas dname with
| None -> "its cases"
| Some u ->
String.concat ", "
- (List.map (fun (c : Tast.variant) -> uname ^ "." ^ c.Tast.vname)
+ (List.map (fun (c : Tast.variant) -> dname ^ "." ^ c.Tast.vname)
u.Tast.cases)
-and first_case_name env uname =
- match Hashtbl.find_opt env.unions uname with
+and first_case_name env dname =
+ match Hashtbl.find_opt env.datas dname with
| Some { Tast.cases = c :: _; _ } -> c.Tast.vname
| _ -> "Case"
(* [(U.C {.f v ...})]. The fields are checked and filled in exactly as a
struct's are — same ZII, same duplicate and unknown-field refusals — and the
only difference is the node at the end and the type it carries. *)
-and check_case ctx ~want loc uname (c : Tast.variant) kvs =
- let full = uname ^ "." ^ c.Tast.vname in
+and check_case ctx ~want loc dname (c : Tast.variant) kvs =
+ let full = dname ^ "." ^ c.Tast.vname in
let seen = Hashtbl.create 8 in
List.iter
(fun (k, (v : Ast.expr)) ->
@@ -2603,7 +2603,7 @@ and check_case ctx ~want loc uname (c : Tast.variant) kvs =
| None -> ());
if Tast.vfield_index c k = None then
Loc.failk "check/unknown-field" v.Ast.loc
- ~notes:(declared_note ctx.env uname)
+ ~notes:(declared_note ctx.env dname)
"%s has no field %s" full k;
Hashtbl.add seen k v)
kvs;
@@ -2616,7 +2616,7 @@ and check_case ctx ~want loc uname (c : Tast.variant) kvs =
c.Tast.vfields
in
expect loc ~want
- (mk loc (Types.Named uname) (Tast.MakeCase (uname, c.Tast.vname, fields)))
+ (mk loc (Types.Named dname) (Tast.MakeCase (dname, c.Tast.vname, fields)))
and check_arr ctx ~want loc items =
let elem_want =
@@ -2650,17 +2650,17 @@ and check_arr ctx ~want loc items =
and check_match ctx ?(tail = false) ?want loc scrutinee arms =
let s = check ctx scrutinee in
- (* What the arms are alternatives over. An [Option] is a two-case union
+ (* What the arms are alternatives over. An [Option] is a two-case data type
wearing a special coat, so the two shapes below are the same shape: a set
of case names, an arity and a payload type per case, and a tag. Keeping
- them apart here rather than desugaring [Option] into a declared union is
- deliberate — [Option] is generic and no declared union is, so the coat is
+ them apart here rather than desugaring [Option] into a declared data type is
+ deliberate — [Option] is generic and no declared data type is, so the coat is
the part that cannot yet be taken off. *)
let subject =
match s.Tast.ty with
| Types.Option t -> `Option t
- | Types.Named n when Hashtbl.mem ctx.env.unions n ->
- `Union (Hashtbl.find ctx.env.unions n)
+ | Types.Named n when Hashtbl.mem ctx.env.datas n ->
+ `Data (Hashtbl.find ctx.env.datas n)
(* An enum is the one scrutinee that is not a milestone away: it is an i32
at run time and its members are all known, so the arms would be a chain
of [=] with an exhaustiveness check over [env.enums] — a desugaring, not
@@ -2674,7 +2674,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
of (= k :member), but a keyword has no case in the pattern type yet. \
Use cond" n
| other ->
- fail loc "match works on an Option or a union, not on %s"
+ fail loc "match works on an Option or a data type, not on %s"
(Types.to_string other)
in
(* Which case each arm names, and the type of each name it binds. This is the
@@ -2691,13 +2691,14 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
| `Option _, Ast.Pctor (c, _) ->
fail a.Ast.aloc
"%s is not a case of Option — the cases are Some and None" c
- | `Union u, Ast.Pctor (c, names) ->
+ | `Data u, Ast.Pctor (c, names) ->
(* A pattern names the case bare: the scrutinee's type already says which
- union, so [(Node l r)] is unambiguous even where two unions share the
- case name. The qualified spelling is accepted too, since that is how
+ data type, so [(Node l r)] is unambiguous even where two data types
+ share the case name. The qualified spelling is accepted too, since
+ that is how
the value was written and writing it again should not be an error. *)
let bare =
- let full = u.Tast.uname ^ "." in
+ let full = u.Tast.dname ^ "." in
let n = String.length full in
if String.length c > n && String.sub c 0 n = full then
String.sub c n (String.length c - n)
@@ -2706,7 +2707,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
(match Tast.case_index u bare with
| None ->
fail a.Ast.aloc "%s is not a case of %s — the cases are %s" c
- u.Tast.uname
+ u.Tast.dname
(String.concat ", "
(List.map (fun (v : Tast.variant) -> v.Tast.vname) u.Tast.cases))
| Some (_, v) ->
@@ -2718,7 +2719,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
fail a.Ast.aloc
"%s.%s has %d field%s, and this pattern binds %d — a case pattern \
binds every field, in declaration order (%s)"
- u.Tast.uname bare (List.length v.Tast.vfields)
+ u.Tast.dname bare (List.length v.Tast.vfields)
(if List.length v.Tast.vfields = 1 then "" else "s")
(List.length names)
(String.concat " "
@@ -2732,7 +2733,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
let seen = Hashtbl.create 8 in
let saw_wild = ref false in
(* The same rule as [if], and for the same reason: the arms are alternatives,
- so each is checked from the state before the match and the union of what
+ so each is checked from the state before the match and the data type of what
they moved survives the join. Checked in sequence against one mutating set
they would report the second arm's (free v) as a use after the first arm's
move, which is a legal program refused. *)
@@ -2772,25 +2773,25 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
ctx.dead <- !joined;
(* Exhaustiveness is refused, not defaulted. A match that silently fell
through would have to produce a value of the match's type out of nothing,
- and there is no such value for most types; and the case a union grows
+ and there is no such value for most types; and the case a data type grows
tomorrow is exactly the one a reader wants to be told about today. A [_]
arm is the way to say "the rest", written where it can be seen. *)
let missing =
match subject with
| `Option _ -> List.filter (fun c -> not (Hashtbl.mem seen c)) [ "Some"; "None" ]
- | `Union u ->
+ | `Data u ->
List.filter_map
(fun (c : Tast.variant) ->
if Hashtbl.mem seen c.Tast.vname then None
- else Some (u.Tast.uname ^ "." ^ c.Tast.vname))
+ else Some (u.Tast.dname ^ "." ^ c.Tast.vname))
u.Tast.cases
in
if not !saw_wild && missing <> [] then
- (* The union's declaration, because that is where the case list this match
+ (* The data type's declaration, because that is where the case list this match
failed to cover actually lives, and because adding a case there is what
makes a match non-exhaustive in the first place. *)
Loc.failk "check/non-exhaustive-match" loc
- ~notes:(match subject with `Union u -> declared_note ctx.env u.Tast.uname
+ ~notes:(match subject with `Data u -> declared_note ctx.env u.Tast.dname
| _ -> [])
"this match is not exhaustive — %s %s no arm. Add %s, or a _ arm for \
the rest"
@@ -2810,15 +2811,15 @@ and struct_target ctx (target : Ast.expr) : Tast.expr * string =
| Types.Named n when Hashtbl.mem ctx.env.structs n -> t, n
| Types.Ptr (Types.Named n) when Hashtbl.mem ctx.env.structs n ->
mk t.Tast.loc (Types.Named n) (Tast.Deref t), n
- (* A union's fields belong to one case, and which case it is holding is only
- known after the tag has been read. [.field] would have to be a read that
- might be reading something else, so it is not one: [match] is how a union
- is opened, and it binds the fields it has proved are there. *)
+ (* A data type's fields belong to one case, and which case it is holding is
+ only known after the tag has been read. [.field] would have to be a read
+ that might be reading something else, so it is not one: [match] is how a
+ data type is opened, and it binds the fields it has proved are there. *)
| (Types.Named n | Types.Ptr (Types.Named n))
- when Hashtbl.mem ctx.env.unions n ->
+ when Hashtbl.mem ctx.env.datas n ->
fail target.Ast.loc
- "%s is a union, and a union's fields belong to a case — which one it is \
- holding is what the tag says, so they are reached by (match ...), \
+ "%s is a data type, and a data type's fields belong to a case — which \
+ one it is holding is what the tag says, so they are reached by (match ...), \
whose arms bind the fields of the case they matched"
n
| other ->
@@ -3174,9 +3175,9 @@ and file_guard ctx loc ~path_slot ~op mk_steps =
[ mk loc Types.Unit (Tast.While (notok (), [ body ], [])) ]))
(* Is this bare symbol the name of a type? Every table [resolve_name] will look
- in, and the union table is one of them: a union is [Named] exactly as a
+ in, and the data type table is one of them: a data type is [Named] exactly as a
struct is, so (vec-new Form) is as ordinary as (vec-new Cell). It was left
- out when unions landed, which made the prelude's own (vec-new Form) fail
+ out when data types landed, which made the prelude's own (vec-new Form) fail
with "nothing here says what (vec-new) is a Vec of" — a message about a
missing annotation for a program that had written one. One list, read by
both callers, so the next kind of type added cannot be added to one of
@@ -3191,7 +3192,7 @@ and type_named ctx n =
|| List.mem_assoc n ctx.env.subst
|| List.mem n Types.primitive_names
|| Hashtbl.mem ctx.env.structs n
- || Hashtbl.mem ctx.env.unions n
+ || Hashtbl.mem ctx.env.datas n
|| Hashtbl.mem ctx.env.enums n
|| Hashtbl.mem ctx.env.aliases n
@@ -4758,7 +4759,7 @@ and named_call ctx ~want loc name args =
let rc =
{ Render.structs =
Hashtbl.fold (fun _ v acc -> v :: acc) ctx.env.structs [];
- unions = Hashtbl.fold (fun _ v acc -> v :: acc) ctx.env.unions [];
+ datas = Hashtbl.fold (fun _ v acc -> v :: acc) ctx.env.datas [];
enums = Hashtbl.fold (fun k v acc -> (k, v) :: acc) ctx.env.enums [];
emit = emitter;
(* [println] never follows a pointer, and the allocation registry does
@@ -4900,18 +4901,18 @@ and named_call ctx ~want loc name args =
let args = map2_lr (fun p a -> check ctx ~want:p a) params args in
expect loc ~want (mk loc ret (Tast.Call (name, args)))
| None ->
- if Hashtbl.mem ctx.env.unions name then
+ if Hashtbl.mem ctx.env.datas name then
fail loc
- "%s is a union type — a union value names the case too, as (%s.%s {.field value ...})"
+ "%s is a data type — a data type value names the case too, as (%s.%s {.field value ...})"
name name (first_case_name ctx.env name)
else if Hashtbl.mem ctx.env.cases name then
(* [(U.C)] and [(C)]: a case written as a call. Both are how someone
reaches for a constructor, and neither is one. *)
- let uname, c = Hashtbl.find ctx.env.cases name in
+ let dname, c = Hashtbl.find ctx.env.cases name in
fail loc
- "%s is a case of the union %s — write (%s.%s {.field value ...}), \
+ "%s is a case of the data type %s — write (%s.%s {.field value ...}), \
or %s.%s on its own when it has no fields"
- name uname uname c.Tast.vname uname c.Tast.vname
+ name dname dname c.Tast.vname dname c.Tast.vname
else if Hashtbl.mem ctx.env.structs name then
fail loc
"%s is a type — a struct value is written (%s {.field value ...})"
@@ -5161,7 +5162,7 @@ let rec const_int env (e : Ast.expr) : int64 option =
let collect env (decls : Ast.decl list) =
(* One pass over every declaration kind before any of the others, because
- the tables below are per-kind — structs, unions, aliases, enums, functions
+ the tables below are per-kind — structs, data types, aliases, enums, functions
and globals each have their own — and a collision between two of them
would otherwise be found by LLVM, as [redefinition of function
'@flan.item'], or not at all. A [defn item] and a [defvar item] are two
@@ -5190,9 +5191,9 @@ let collect env (decls : Ast.decl list) =
| Ast.Defstruct (n, _) ->
Hashtbl.replace env.locs n d.Ast.dloc;
Hashtbl.replace env.structs n { Tast.sname = n; fields = [] }
- | Ast.Defunion (n, _) ->
+ | Ast.Defdata (n, _) ->
Hashtbl.replace env.locs n d.Ast.dloc;
- Hashtbl.replace env.unions n { Tast.uname = n; cases = [] }
+ Hashtbl.replace env.datas n { Tast.dname = n; cases = [] }
| Ast.Defalias (n, t) -> Hashtbl.replace env.aliases n t
| _ -> ())
decls;
@@ -5310,15 +5311,15 @@ let collect env (decls : Ast.decl list) =
(Types.to_string f.Tast.fty))
fields;
Hashtbl.replace env.structs n { Tast.sname = n; fields }
- | Ast.Defunion (n, vs) ->
- (* A union with no cases has no value, so nothing could ever be given
+ | Ast.Defdata (n, vs) ->
+ (* A data type with no cases has no value, so nothing could ever be given
one, and a parameter of that type would be a function nothing can
call. It parses; it is refused here rather than surviving to a
layout with a tag and no case for the tag to name. *)
if vs = [] then
fail loc
- "%s declares no cases, so no value of it can exist — a union is \
- (defunion %s [(Case [field Type ...]) ...])" n n;
+ "%s declares no cases, so no value of it can exist — a data type is \
+ (defdata %s [(Case [field Type ...]) ...])" n n;
let cnames = List.map (fun (v : Ast.variant) -> v.Ast.vname) vs in
if List.length (List.sort_uniq compare cnames) <> List.length cnames
then fail loc "%s declares the same case twice" n;
@@ -5334,18 +5335,19 @@ let collect env (decls : Ast.decl list) =
n v.Ast.vname;
let vfields = List.map field v.Ast.vfields in
(* The same refusal a struct field gets, for the same reason
- and in the same words: a union case's fields are a struct,
- the union copies bytewise on assignment, and recursive
+ and in the same words: a data type case's fields are a struct,
+ the data type copies bytewise on assignment, and recursive
teardown arrives with [drop]. Refusing it here rather than
at a use keeps the two declarations honest with each other
- — a union that could hold a Vec where a struct could not
+ — a data type that could hold a Vec where a struct could not
would be a hole in the same rule. *)
List.iter
(fun (f : Tast.field) ->
if Types.is_move_only f.Tast.fty then
fail v.Ast.vloc
"%s.%s's field %s is %s, which is move-only, and a \
- union case that owns one makes the union move-only \
+ data type case that owns one makes the data type \
+ move-only \
too — transitively, with recursive teardown. That \
rule arrives with drop (step 5 in NEXT.md); until \
then hold the %s in a local and pass it"
@@ -5355,7 +5357,7 @@ let collect env (decls : Ast.decl list) =
{ Tast.vname = v.Ast.vname; vfields })
vs
in
- Hashtbl.replace env.unions n { Tast.uname = n; cases };
+ Hashtbl.replace env.datas n { Tast.dname = n; cases };
List.iter
(fun (c : Tast.variant) ->
Hashtbl.replace env.cases (n ^ "." ^ c.Tast.vname) (n, c);
@@ -5451,7 +5453,7 @@ let check_finite env =
match Hashtbl.find_opt env.structs name with
| Some s -> List.iter (fun (f : Tast.field) -> ty seen f.Tast.fty) s.Tast.fields
| None ->
- match Hashtbl.find_opt env.unions name with
+ match Hashtbl.find_opt env.datas name with
| None -> ()
| Some u ->
List.iter
@@ -5464,7 +5466,7 @@ let check_finite env =
| _ -> ()
in
Hashtbl.iter (fun n _ -> walk [] n) env.structs;
- Hashtbl.iter (fun n _ -> walk [] n) env.unions
+ Hashtbl.iter (fun n _ -> walk [] n) env.datas
(* ── Declarations: pass 2, check bodies ────────────────────────────── *)
@@ -5661,7 +5663,7 @@ let check_global env (d : Ast.decl) : Tast.global option =
| Ast.Zeroed -> { Tast.e = Tast.Zero ty; ty; loc = d.Ast.dloc }
| Ast.Uninit ->
(* Everywhere else [uninit] is an opt-out from ZII and the bytes are
- whatever they were: a garbage f64 is a garbage number. A union is
+ whatever they were: a garbage f64 is a garbage number. A data type is
the one type where that is qualitatively worse — the tag steers
control flow, a tag no case names falls past every comparison in a
[match], and the block after them is [unreachable], which LLVM is
@@ -5669,13 +5671,13 @@ let check_global env (d : Ast.decl) : Tast.global option =
becomes "the optimiser may do anything" is refused by name, and the
zeroed form, which is the first declared case, is named beside it. *)
(match ty with
- | Types.Named un when Hashtbl.mem env.unions un ->
+ | Types.Named un when Hashtbl.mem env.datas un ->
fail d.Ast.dloc
- "%s is a union, and uninit on one is refused: its tag steers \
+ "%s is a data type, and uninit on one is refused: its tag steers \
every match, and a tag no case names has no arm to reach. Drop \
the uninit — a zeroed %s is %s, which is a real case"
(Types.to_string ty) un
- (match Hashtbl.find_opt env.unions un with
+ (match Hashtbl.find_opt env.datas un with
| Some { Tast.cases = c :: _; _ } -> un ^ "." ^ c.Tast.vname
| _ -> "its first case")
| _ -> ());
@@ -5815,7 +5817,7 @@ let build_program ~keep_going (decls : Ast.decl list) : Tast.program * env =
|> List.sort (fun (a : Tast.extern) b -> String.compare a.Tast.esym b.Tast.esym)
in
({ Tast.structs = values (fun (s : Tast.structure) -> s.Tast.sname) env.structs;
- unions = values (fun (u : Tast.union) -> u.Tast.uname) env.unions;
+ datas = values (fun (u : Tast.data) -> u.Tast.dname) env.datas;
globals; externs; fns; cshim },
env)
diff --git a/lib/dev.ml b/lib/dev.ml
index 69139c4..a0f4d25 100644
--- a/lib/dev.ml
+++ b/lib/dev.ml
@@ -735,26 +735,27 @@ let layout t ~ty =
| None ->
(* Two types the checker knows and this op cannot describe. An enum's
members are erased to i32 before [Tast.program] exists, which is the
- same fact that makes a defenum unreloadable; a union is declared and
+ same fact that makes a defenum unreloadable; a data type is declared and
has no values yet. Either way, saying which kind it is beats "no such
type" for a name that plainly exists. *)
if Hashtbl.mem t.session.Session.env.Check.enums ty then
error (ty ^ " is an enum, not a struct; its members are erased to i32")
else if
- List.exists (fun (u : Tast.union) -> String.equal u.Tast.uname ty)
- t.session.Session.program.Tast.unions
+ List.exists (fun (u : Tast.data) -> String.equal u.Tast.dname ty)
+ t.session.Session.program.Tast.datas
then
- (* Unions have landed, so "milestone 6" was stale — but what replaces it
- is not a layout. This op's reply is a flat [:fields] list, and a union
+ (* Data types have landed, so "milestone 6" was stale — but what replaces it
+ is not a layout. This op's reply is a flat [:fields] list, and a data type
is a tag and one payload per case: there is no one field list to
answer with, and flattening the cases into one would describe storage
no value ever has. So it says which kind of type this is, and where
the question it was probably asked for *is* answered — the renderer
- walks a union now, so a union value prints in a frame's locals and at
+ walks a data type now, so a data type value prints in a frame's
+ locals and at
`C-x C-e' with its case and that case's fields. *)
error
(ty
- ^ " is a union, not a struct; a union is a tag and one payload per case, so it has no single field list for this op to answer with. Its value renders with its case and fields in a frame's locals and at C-x C-e")
+ ^ " is a data type, not a struct; a data type is a tag and one payload per case, so it has no single field list for this op to answer with. Its value renders with its case and fields in a frame's locals and at C-x C-e")
else
let suffix = "/" ^ ty in
let candidates =
@@ -1221,7 +1222,7 @@ let render_addr (s : Session.t) ~addr ~(ty : Types.t)
let extra = ref [] and nslots = ref 0 in
let c =
{ Render.structs = s.Session.program.Tast.structs;
- unions = s.Session.program.Tast.unions;
+ datas = s.Session.program.Tast.datas;
enums =
Hashtbl.fold (fun k v acc -> (k, v) :: acc) s.Session.env.Check.enums [];
emit = Session.dev_emitter;
diff --git a/lib/emit.ml b/lib/emit.ml
index 337ea9e..a5aeaad 100644
--- a/lib/emit.ml
+++ b/lib/emit.ml
@@ -220,12 +220,12 @@ type m = {
out : Buffer.t;
strs : Buffer.t; (* string literal constants *)
structs : (string, Tast.structure) Hashtbl.t;
- (* The declared unions, by name. [Types.Named] covers both a struct and a
- union, so which table the name is in is the only thing that says which
+ (* The declared data types, by name. [Types.Named] covers both a struct and a
+ data type, so which table the name is in is the only thing that says which
this is — the same arrangement the checker uses, and for the same reason:
- a union is a type like any other everywhere except at its layout, its
+ a data type is a type like any other everywhere except at its layout, its
construction and its match. *)
- unions : (string, Tast.union) Hashtbl.t;
+ datas : (string, Tast.data) Hashtbl.t;
globals : (string, Types.t) Hashtbl.t;
(* Flan name -> C symbol, for the foreign functions. A call to one names the
symbol directly; there is no thunk. *)
@@ -300,7 +300,7 @@ let rec lay m (t : Types.t) : int * int =
in
s, a
| None ->
- match Hashtbl.find_opt m.unions n with
+ match Hashtbl.find_opt m.datas n with
| Some u ->
(* The tag then the payload, as one struct, so the answer is the same
arithmetic every other aggregate here gets rather than a second
@@ -332,11 +332,11 @@ and lay_fields m tys =
tys;
align_up !off !al, !al, List.rev !rev
-(* The size and alignment of a union's payload: room for the largest case, with
+(* The size and alignment of a data type's payload: room for the largest case, with
the alignment the widest member of any case needs, and the size rounded up
- to it so the blob divides evenly into [k x iA]. A union of payload-less
+ to it so the blob divides evenly into [k x iA]. A data type of payload-less
cases has a zero-size payload and is a bare tag. *)
-and payload_lay m (u : Tast.union) : int * int =
+and payload_lay m (u : Tast.data) : int * int =
let align = ref 1 and size = ref 0 in
List.iter
(fun (c : Tast.variant) ->
@@ -440,7 +440,7 @@ let rec dty m d (t : Types.t) : int =
(List.map (fun (fl : Tast.field) -> (fl.Tast.fname, fl.Tast.fty))
st.Tast.fields)
| None ->
- match Hashtbl.find_opt m.unions sn with
+ match Hashtbl.find_opt m.datas sn with
(* The truth about the bytes, and nothing cleverer: a tag and a blob.
DWARF 5 has DW_TAG_variant_part for exactly this, and lldb's C
support does not use it — a debugger that was handed one would
@@ -1110,8 +1110,8 @@ and value_at f (e : Tast.expr) : string =
ins f "store %s %s, ptr %s" (ll ty) v' ptr);
"zeroinitializer"
| Tast.Make (_, fields) -> aggregate f e.Tast.ty fields
- | Tast.MakeCase (uname, case, fields) ->
- emit_make_case f uname case fields
+ | Tast.MakeCase (dname, case, fields) ->
+ emit_make_case f dname case fields
| Tast.CaseField (target, case, i) ->
load f (case_field_addr f target case i) e.Tast.ty
| Tast.Arr items -> aggregate f e.Tast.ty items
@@ -1312,26 +1312,26 @@ and place f (p : Tast.place) : string * Types.t =
(* A struct or fixed-array value, built field by field from zeroinitializer.
The checker already filled the omitted fields in with Zero, so this is
simply every field in declaration order. *)
-(* A union value, built in memory rather than with [insertvalue], because the
+(* A data type value, built in memory rather than with [insertvalue], because the
payload's declared type is a blob of integers and the case's fields are not:
the two views of the same bytes are what a gep expresses and what a chain of
[insertvalue] cannot. The alloca is what [mem2reg] removes when nobody takes
an address of it. *)
-and emit_make_case f uname case fields =
- let ty = Types.Named uname in
- let u = Hashtbl.find f.md.unions uname in
+and emit_make_case f dname case fields =
+ let ty = Types.Named dname in
+ let u = Hashtbl.find f.md.datas dname in
let tag = match Tast.case_index u case with
| Some (i, _) -> i
- | None -> failwith ("no case " ^ case ^ " of " ^ uname)
+ | None -> failwith ("no case " ^ case ^ " of " ^ dname)
in
let tmp = alloca f ty in
ins f "store %s zeroinitializer, ptr %s" (ll ty) tmp;
let tp = fresh f in
- ins f "%s = getelementptr inbounds %s, ptr %s, i32 0, i32 0" tp (sname uname) tmp;
+ ins f "%s = getelementptr inbounds %s, ptr %s, i32 0, i32 0" tp (sname dname) tmp;
ins f "store i32 %d, ptr %s" tag tp;
if fields <> [] then begin
- let pp = payload_addr f uname tmp in
- let cty = sname (uname ^ "." ^ case) in
+ let pp = payload_addr f dname tmp in
+ let cty = sname (dname ^ "." ^ case) in
List.iteri
(fun i (p : Tast.expr) ->
let v = value f p in
@@ -1342,26 +1342,26 @@ and emit_make_case f uname case fields =
end;
load f tmp ty
-(* The payload blob's address. A union with no payload has no field 1, so this
+(* The payload blob's address. A data type with no payload has no field 1, so this
is only ever reached for one that has fields to reach. *)
-and payload_addr f uname base =
+and payload_addr f dname base =
let p = fresh f in
- ins f "%s = getelementptr inbounds %s, ptr %s, i32 0, i32 1" p (sname uname) base;
+ ins f "%s = getelementptr inbounds %s, ptr %s, i32 0, i32 1" p (sname dname) base;
p
-(* The address of one field of one case of a union value. The single place in
+(* The address of one field of one case of a data type value. The single place in
this backend that knows how a payload is read, so [match]'s binds and the
structural printer cannot come to different conclusions about it. *)
and case_field_addr f (target : Tast.expr) case i =
- let uname = match target.Tast.ty with
+ let dname = match target.Tast.ty with
| Types.Named n -> n
| t -> failwith ("case field of " ^ Types.to_string t)
in
let base = addr f target in
- let pp = payload_addr f uname base in
+ let pp = payload_addr f dname base in
let p = fresh f in
ins f "%s = getelementptr inbounds %s, ptr %s, i32 0, i32 %d"
- p (sname (uname ^ "." ^ case)) pp i;
+ p (sname (dname ^ "." ^ case)) pp i;
p
and aggregate f ty parts =
@@ -1787,17 +1787,17 @@ and emit_while f c body latch =
and emit_match f ty scrut arms =
(* The two subjects are the same shape and are read differently: an [Option]
is an SSA aggregate with an i8 tag and its payload in field 1, a declared
- union is read through its address because its payload is a blob that has
+ data type is read through its address because its payload is a blob that has
to be reinterpreted. So the tag and the binds are each produced by one of
two small functions and everything else below is shared. *)
- let uname =
+ let dname =
match scrut.Tast.ty with
- | Types.Named n when Hashtbl.mem f.md.unions n -> Some n
+ | Types.Named n when Hashtbl.mem f.md.datas n -> Some n
| Types.Option _ -> None
| t -> failwith ("match on " ^ Types.to_string t)
in
let tag, read_tag, bind_of =
- match uname with
+ match dname with
| None ->
let sv = value f scrut in
let sty = ll scrut.Tast.ty in
@@ -1813,7 +1813,7 @@ and emit_match f ty scrut arms =
ins f "store %s %s, ptr %s" (ll payload_ty) v f.slots.(slot);
bind_slot f slot)
| Some n ->
- let u = Hashtbl.find f.md.unions n in
+ let u = Hashtbl.find f.md.datas n in
(* Evaluated once, into a place, so that a scrutinee that is a call is
not re-run per arm. [addr] already spills a non-place for us. *)
let base = addr f scrut in
@@ -2551,22 +2551,22 @@ let rec const m (e : Tast.expr) =
| _ -> "{ " ^ String.concat ", " inner ^ " }")
| Tast.Some_ v ->
Printf.sprintf "{ i8 1, %s %s }" (ll v.Tast.ty) (const m v)
- (* A union's payload is declared as a blob of integers, so a constant of one
+ (* A data type's payload is declared as a blob of integers, so a constant of one
would have to be the case's fields *serialised into those integers* —
which is a byte-level encoder this compiler does not have, and which could
not express a string field at all, since that is a pointer the linker has
to relocate and a byte array has nowhere to put a relocation. Refused by
name, here, where the rest of the same rule is. A zeroed global is fine
and needs none of this: it is the first declared case, all-bytes-zero. *)
- | Tast.MakeCase (uname, case, _) ->
+ | Tast.MakeCase (dname, case, _) ->
fail e.Tast.loc
- "a global cannot be initialised with %s.%s — a union's payload is a \
+ "a global cannot be initialised with %s.%s — a data type's payload is a \
blob, and writing a case into one at link time needs a byte-level \
encoder that does not exist (a string field could not be encoded at \
all). Declare the global zeroed, which is %s.%s, and assign the case \
you meant in a function"
- uname case uname
- (match Hashtbl.find_opt m.unions uname with
+ dname case dname
+ (match Hashtbl.find_opt m.datas dname with
| Some { Tast.cases = c :: _; _ } -> c.Tast.vname
| _ -> "its first case")
| _ ->
@@ -2807,7 +2807,7 @@ let new_module ~checks ~dev ~known ?(debug = false) ?(sanitize = false)
(p : Tast.program) =
let m = {
out = Buffer.create 8192; strs = Buffer.create 512;
- structs = Hashtbl.create 16; unions = Hashtbl.create 16;
+ structs = Hashtbl.create 16; datas = Hashtbl.create 16;
globals = Hashtbl.create 16;
externs = Hashtbl.create 32;
checks; dev; known; nstr = 0; nfi = 0; sanitize;
@@ -2815,8 +2815,8 @@ let new_module ~checks ~dev ~known ?(debug = false) ?(sanitize = false)
} in
List.iter (fun (s : Tast.structure) -> Hashtbl.replace m.structs s.Tast.sname s)
p.Tast.structs;
- List.iter (fun (u : Tast.union) -> Hashtbl.replace m.unions u.Tast.uname u)
- p.Tast.unions;
+ List.iter (fun (u : Tast.data) -> Hashtbl.replace m.datas u.Tast.dname u)
+ p.Tast.datas;
List.iter (fun (g : Tast.global) -> Hashtbl.replace m.globals g.Tast.gname g.Tast.gty)
p.Tast.globals;
List.iter (fun (e : Tast.extern) -> Hashtbl.replace m.externs e.Tast.ename e.Tast.esym)
@@ -2828,7 +2828,7 @@ let new_module ~checks ~dev ~known ?(debug = false) ?(sanitize = false)
(String.concat ", "
(List.map (fun (f : Tast.field) -> ll f.Tast.fty) s.Tast.fields))))
p.Tast.structs;
- (* A union is a tag and a blob, and each of its cases is a struct laid over
+ (* A data type is a tag and a blob, and each of its cases is a struct laid over
the blob. Both are emitted as named types so that every reader — a
construction, a match arm, the structural printer — geps rather than
computing byte offsets of its own.
@@ -2840,25 +2840,25 @@ let new_module ~checks ~dev ~known ?(debug = false) ?(sanitize = false)
the point — the macro expander's [Form] has to be the same bytes in the
compiler and in the dlopened macro. *)
List.iter
- (fun (u : Tast.union) ->
+ (fun (u : Tast.data) ->
List.iter
(fun (c : Tast.variant) ->
Buffer.add_string m.out
(Printf.sprintf "%s = type { %s }\n"
- (sname (u.Tast.uname ^ "." ^ c.Tast.vname))
+ (sname (u.Tast.dname ^ "." ^ c.Tast.vname))
(String.concat ", "
(List.map (fun (f : Tast.field) -> ll f.Tast.fty)
c.Tast.vfields))))
u.Tast.cases)
- p.Tast.unions;
+ p.Tast.datas;
List.iter
- (fun (u : Tast.union) ->
+ (fun (u : Tast.data) ->
let size, align = payload_lay m u in
Buffer.add_string m.out
- (Printf.sprintf "%s = type { i32%s }\n" (sname u.Tast.uname)
+ (Printf.sprintf "%s = type { i32%s }\n" (sname u.Tast.dname)
(if size = 0 then ""
else Printf.sprintf ", [%d x i%d]" (size / align) (align * 8))))
- p.Tast.unions;
+ p.Tast.datas;
Buffer.add_char m.out '\n';
(* The foreign declarations. Every struct that crosses this boundary was
flattened by a C shim, so each of these is scalars only and no calling
diff --git a/lib/expand.ml b/lib/expand.ml
index 9cef480..a179009 100644
--- a/lib/expand.ml
+++ b/lib/expand.ml
@@ -19,10 +19,10 @@
offset 8. Those three numbers are the whole agreement between this file and
the compiled macro, and they are not taken on trust — test_acceptance.ml's
"Form's image format" asks LLVM for each of them through the same ptrtoint
- oracle the DWARF offsets go through. Change the prelude's defunion and that
+ oracle the DWARF offsets go through. Change the prelude's defdata and that
test says which number moved.
- The tag is the case's position in the prelude's (defunion Form ...), which
+ The tag is the case's position in the prelude's (defdata Form ...), which
is why that list is a layout contract and says so. *)
let form_size = 24
@@ -48,7 +48,7 @@ let tag_of_int = function
failwith
(Printf.sprintf
"a macro returned a Form with tag %ld, and Form has nine cases. The \
- prelude's (defunion Form ...) and lib/expand.ml's tag list are one \
+ prelude's (defdata Form ...) and lib/expand.ml's tag list are one \
contract and have come apart"
n)
diff --git a/lib/load.ml b/lib/load.ml
index b1fc2d3..c5382be 100644
--- a/lib/load.ml
+++ b/lib/load.ml
@@ -328,8 +328,9 @@ let qualify_decl owned alias (d : Ast.decl) : Ast.decl =
than anything a user wrote. *)
| Ast.Import (a, _) ->
fail loc "internal: the import of %s was not resolved before qualifying" a
- | Ast.Defunion (n, _) ->
- fail loc "%s is a union, and an imported union is not implemented yet \
+ | Ast.Defdata (n, _) ->
+ fail loc "%s is a data type, and an imported data type is not \
+ implemented yet \
(milestone 4)" n
in
{ d with Ast.d = k }
@@ -358,7 +359,7 @@ let qualify_decl owned alias (d : Ast.decl) : Ast.decl =
binders tracked are the ones a macro body can hold — its own parameter,
[let], [loop], [fn] and [dotimes]. A [match] pattern's names and a
[restart-case] clause's parameters are not tracked, which is a gap and a
- narrow one: it takes a macro body that both destructures a union and binds a
+ narrow one: it takes a macro body that both destructures a data type and binds a
name the package also declares at the top level. *)
let rec form_syms (f : Form.t) acc =
@@ -593,7 +594,7 @@ let decl_uses acc (d : Ast.decl) =
| Ast.Package _ | Ast.Import _ | Ast.Defenum _ -> ()
| Ast.Defalias (_, t) -> texpr_uses acc t
| Ast.Defstruct (_, fs) -> List.iter field fs
- | Ast.Defunion (_, vs) ->
+ | Ast.Defdata (_, vs) ->
List.iter (fun (v : Ast.variant) -> List.iter field v.Ast.vfields) vs
| Ast.Defn f -> fn f
(* Both declaration forms name types in their signature and nothing else.
diff --git a/lib/macro.ml b/lib/macro.ml
index 7be9038..26de4f2 100644
--- a/lib/macro.ml
+++ b/lib/macro.ml
@@ -108,7 +108,7 @@ let building = ref false
depends on a macro *removed*. Directly or transitively, because a function
calling a dropped one is as unbuildable as the dropped one itself.
- Only [defn]s are dropped. A [defstruct], [defunion], [defalias], [defenum]
+ Only [defn]s are dropped. A [defstruct], [defdata], [defalias], [defenum]
or [defvar] stays whatever it names: the functions that survive still
mention those types, and a reduced prelude missing them would not check.
There used to be a sharper reason — [Parse.prelude_types] memoised the
diff --git a/lib/parse.ml b/lib/parse.ml
index bdfb134..fbf5edf 100644
--- a/lib/parse.ml
+++ b/lib/parse.ml
@@ -455,7 +455,7 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
a head, which is the same property that makes a quasiquoted macro call
output rather than a dependency. Building a declaration as a value is what
a macro is for. *)
- | Sym ("defmacro" | "defn" | "defvar" | "defconst" | "defstruct" | "defunion"
+ | Sym ("defmacro" | "defn" | "defvar" | "defconst" | "defstruct" | "defdata"
| "defenum" | "defalias" | "import" as name) ->
fail f
"%s is a top-level declaration, not an expression. A quasiquoted one is \
@@ -850,10 +850,26 @@ let rec decl (f : Form.t) : Ast.decl =
| [ n; { v = Vec fs; _ } ] -> mk (Ast.Defstruct (sym n, fields f fs))
| _ -> fail f "defstruct is (defstruct Name [field Type ...])")
- | List ({ v = Sym "defunion"; _ } :: args) ->
+ | List ({ v = Sym "defdata"; _ } :: args) ->
(match args with
- | [ n; { v = Vec vs; _ } ] -> mk (Ast.Defunion (sym n, List.map variant vs))
- | _ -> fail f "defunion is (defunion Name [(Case [field Type ...]) ...])")
+ | [ n; { v = Vec vs; _ } ] -> mk (Ast.Defdata (sym n, List.map variant vs))
+ | _ -> fail f "defdata is (defdata Name [(Case [field Type ...]) ...])")
+
+ (* The tagged sum used to be spelled [defunion], and every file in the tree
+ said so until this rename. The old spelling is not an alias, and it is not
+ silently accepted either: the name is being reserved for a different type
+ — C's untagged union, where the members overlay one another and nothing
+ says which is live — so a [defunion] left behind by a stale file must not
+ keep meaning what it used to. Accepting it as an alias is the one option
+ that cannot be taken: the day the untagged form lands, the same text would
+ go on compiling and would mean the opposite thing, which is the silent
+ misparse the [defn] case below was rewritten to make impossible. *)
+ | List ({ v = Sym "defunion"; _ } :: _) ->
+ Loc.failk "parse/defunion-renamed" f.loc
+ "defunion is now defdata — (defdata Name [(Case [field Type ...]) ...]). \
+ The name defunion is reserved for a different type, so this is renamed \
+ rather than aliased: a file that kept the old spelling would otherwise \
+ go on compiling and mean something else"
(* The slot after the parameters is unconditionally the return type. It used
to be optional, and the parser decided return-type-versus-body by looking
@@ -1060,7 +1076,7 @@ and variant (f : Form.t) : Ast.variant =
| List [ { v = Sym n; _ }; { v = Vec fs; _ } ] ->
{ Ast.vname = n; vfields = fields f fs; vloc = f.loc }
| List [ { v = Sym n; _ } ] -> { Ast.vname = n; vfields = []; vloc = f.loc }
- | _ -> fail f "a union case is Name or (Name [field Type ...])"
+ | _ -> fail f "a data type case is Name or (Name [field Type ...])"
(* Macro expansion, which runs over [Form] and therefore before anything in
this file. It cannot be called directly: expanding a macro means compiling
diff --git a/lib/prelude.ml b/lib/prelude.ml
index 473026a..3ed65e6 100644
--- a/lib/prelude.ml
+++ b/lib/prelude.ml
@@ -1466,9 +1466,9 @@ let source = {flan|
;; site attached to what a macro produces", and it is what the queued
;; structured-error work will read.
;;
-;; Case order is the tag order (docs/BUILT.md, unions), so this list is a layout
+;; Case order is the tag order (docs/BUILT.md, data types), so this list is a layout
;; contract with lib/expand.ml's marshaller and may not be reordered.
-(defunion Form
+(defdata Form
[(Sym [s string])
(Kw [s string])
(Int [i i64])
diff --git a/lib/reach.ml b/lib/reach.ml
index 331f4cf..d9f0ec8 100644
--- a/lib/reach.ml
+++ b/lib/reach.ml
@@ -18,7 +18,7 @@
calls [@InitWindow] only moves the failure from the linker's argument
list to its symbol table.
- Only [fns] and [externs] are pruned. Globals, structs and unions stay:
+ Only [fns] and [externs] are pruned. Globals, structs and data types stay:
a dropped function is a loud link error, a dropped global would be a
silently different program, and an unreferenced global is bytes in BSS that
cost nothing. A [defvar brush rl/Texture2D] in a headless build is exactly
diff --git a/lib/render.ml b/lib/render.ml
index 218c1e3..04a2c05 100644
--- a/lib/render.ml
+++ b/lib/render.ml
@@ -57,10 +57,11 @@ type pointers = {
type ctx = {
structs : Tast.structure list;
- (* The declared unions. [Types.Named] covers a struct and a union alike, so
+ (* The declared data types. [Types.Named] covers a struct and a data type
+ alike, so
which list the name is in is what says which this is — the same
arrangement the checker and the emitter use. *)
- unions : Tast.union list;
+ datas : Tast.data list;
enums : (string * (string * int64) list) list;
emit : emitter;
(* [None] in a build with no registry to ask, which is every release build
@@ -225,15 +226,15 @@ let rec render c depth (e : Tast.expr) : Tast.expr list =
(Tast.If (is_some,
do_ ((lit "(some " :: render c (depth + 1) some) @ [ lit ")" ]),
lit "none")) ]
- (* A union, printed as the source would write it: the case is recovered
+ (* A data type, printed as the source would write it: the case is recovered
from the tag by a chain of comparisons, exactly as an enum's member name
is, and only the case in hand has its fields read. Reading the others
would be reading a payload that is not there. *)
| Types.Named n
- when List.exists (fun (u : Tast.union) -> String.equal u.Tast.uname n)
- c.unions ->
+ when List.exists (fun (u : Tast.data) -> String.equal u.Tast.dname n)
+ c.datas ->
let u =
- List.find (fun (u : Tast.union) -> String.equal u.Tast.uname n) c.unions
+ List.find (fun (u : Tast.data) -> String.equal u.Tast.dname n) c.datas
in
let tag = { Tast.e = Tast.Field (e, 0); ty = Types.Int Types.I32; loc } in
let one i (v : Tast.variant) otherwise =
@@ -269,7 +270,7 @@ let rec render c depth (e : Tast.expr) : Tast.expr list =
in
unit_ (Tast.If (is, body, otherwise))
in
- (* The fallback is a tag no case names, which only a scribbled-over union
+ (* The fallback is a tag no case names, which only a scribbled-over data type
could hold. Showing the number is more use than showing a case it is
not. *)
let base =
diff --git a/lib/session.ml b/lib/session.ml
index f6b0ba3..89d20bc 100644
--- a/lib/session.ml
+++ b/lib/session.ml
@@ -748,7 +748,7 @@ let render_locals ?(origin = "") t ~frame ~(fn : Tast.fn) ~bound
let extra = ref [] and nslots = ref 0 in
let c =
{ Render.structs = t.program.Tast.structs;
- unions = t.program.Tast.unions;
+ datas = t.program.Tast.datas;
enums = Hashtbl.fold (fun k v acc -> (k, v) :: acc) t.env.Check.enums [];
emit = dev_emitter;
ptrs = Some dev_pointers;
@@ -874,8 +874,8 @@ let render_locals ?(origin = "") t ~frame ~(fn : Tast.fn) ~bound
is not an option. A pointer is still never followed — that is the
renderer's rule and not this mode's. *)
-(* A step, as the editor sends it. [Sfield] on a union carries the case as
- well, because a union's payload is at an offset that depends on which case
+(* A step, as the editor sends it. [Sfield] on a data type carries the case as
+ well, because a data type's payload is at an offset that depends on which case
it is, and the renderer is what told the editor which case this value
currently holds. Guessing the case from a field name that two cases share
would read one case's layout over another's payload. *)
@@ -937,21 +937,21 @@ let step_into t (v : Tast.expr) (s : step) : (Tast.expr, string) result =
| Sfield spec ->
(match ty with
| Types.Named n
- when List.exists (fun (u : Tast.union) -> String.equal u.Tast.uname n)
- t.program.Tast.unions ->
+ when List.exists (fun (u : Tast.data) -> String.equal u.Tast.dname n)
+ t.program.Tast.datas ->
let u =
- List.find (fun (u : Tast.union) -> String.equal u.Tast.uname n)
- t.program.Tast.unions
+ List.find (fun (u : Tast.data) -> String.equal u.Tast.dname n)
+ t.program.Tast.datas
in
- (* The editor spells this `Union.case.field', which is the head the
- renderer wrote — `(Union.case {.field …})' — with the field appended.
+ (* The editor spells this `Type.case.field', which is the head the
+ renderer wrote — `(Type.case {.field …})' — with the field appended.
A bare `case.field' is taken too, since that is the same fact said
shorter. *)
(match String.rindex_opt spec '.' with
| None ->
no
(Printf.sprintf
- "%s is a union: a field of it has to name the case that holds \
+ "%s is a data type: a field of it has to name the case that holds \
it, because the payload's offset depends on which case the \
value is in"
n)
@@ -1045,7 +1045,7 @@ let render_slot ?(origin = "") t ~frame ~(fn : Tast.fn) ~slot ~path
let extra = ref [] and nslots = ref 0 in
let c =
{ Render.structs = t.program.Tast.structs;
- unions = t.program.Tast.unions;
+ datas = t.program.Tast.datas;
enums = Hashtbl.fold (fun k v acc -> (k, v) :: acc) t.env.Check.enums [];
emit = dev_emitter;
ptrs = Some dev_pointers;
@@ -1137,7 +1137,7 @@ let render_globals ?(origin = "") t ~(globals : Tast.global list)
let extra = ref [] and nslots = ref 0 in
let c =
{ Render.structs = t.program.Tast.structs;
- unions = t.program.Tast.unions;
+ datas = t.program.Tast.datas;
enums = Hashtbl.fold (fun k v acc -> (k, v) :: acc) t.env.Check.enums [];
emit = dev_emitter;
ptrs = Some dev_pointers;
@@ -1244,7 +1244,7 @@ let eval_expr ?(origin = "") ?(pause = false) t src : change =
let extra = ref [] and nslots = ref (Array.length base) in
let c =
{ Render.structs = t.program.Tast.structs;
- unions = t.program.Tast.unions;
+ datas = t.program.Tast.datas;
enums = Hashtbl.fold (fun k v acc -> (k, v) :: acc) t.env.Check.enums [];
emit = dev_emitter;
ptrs = Some dev_pointers;
diff --git a/lib/shim.ml b/lib/shim.ml
index 00aed2c..ff5ea89 100644
--- a/lib/shim.ml
+++ b/lib/shim.ml
@@ -46,7 +46,7 @@
struct layout is C's, and [emit.ml] writes no datalayout, so clang applies
the target's own rules to both halves and they land in the same place.
Everything where the two could diverge — a fixed array, a slice, an
- [Option], a map, a union — is refused at the field, by name.
+ [Option], a map, a data type — is refused at the field, by name.
Trusted: that the [defstruct] describes the library's real struct, and that
the [declare-c] signature is the function's real signature. No library
@@ -121,21 +121,21 @@ let out_tmp = "%out"
type env = {
structs : (string, Ast.field list) Hashtbl.t;
enums : (string, unit) Hashtbl.t;
- unions : (string, unit) Hashtbl.t;
+ datas : (string, unit) Hashtbl.t;
aliases : (string, Ast.texpr) Hashtbl.t;
}
let scan (decls : Ast.decl list) =
let env =
{ structs = Hashtbl.create 32; enums = Hashtbl.create 32;
- unions = Hashtbl.create 8; aliases = Hashtbl.create 16 }
+ datas = Hashtbl.create 8; aliases = Hashtbl.create 16 }
in
List.iter
(fun (d : Ast.decl) ->
match d.Ast.d with
| Ast.Defstruct (n, fs) -> Hashtbl.replace env.structs n fs
| Ast.Defenum (n, _) -> Hashtbl.replace env.enums n ()
- | Ast.Defunion (n, _) -> Hashtbl.replace env.unions n ()
+ | Ast.Defdata (n, _) -> Hashtbl.replace env.datas n ()
| Ast.Defalias (n, t) -> Hashtbl.replace env.aliases n t
| _ -> ())
decls;
@@ -190,9 +190,9 @@ let rec cty env ~needed ~loc ~what (t : Ast.texpr) : string =
(* A C enum is an int, and Flan's [Enum] is an i32 — the same thing on
every target this compiles for. *)
"int32_t"
- else if Hashtbl.mem env.unions n then
+ else if Hashtbl.mem env.datas n then
fail loc
- "%s is %s, a union, and a Flan union has no C layout — the shim \
+ "%s is %s, a data type, and a Flan data type has no C layout — the shim \
cannot be generated for it"
what n
else if String.equal n "string" then
diff --git a/lib/tast.ml b/lib/tast.ml
index 5900d77..bba09df 100644
--- a/lib/tast.ml
+++ b/lib/tast.ml
@@ -133,15 +133,16 @@ and expr_kind =
| Addr of place
| Deref of expr
| Make of string * expr list (* struct literal, every field, in order *)
- (* A union value: the union's name, the case's name, and every field of that
- case in declaration order with the omitted ones filled in as [Zero] — the
+ (* A data type value: the data type's name, the case's name, and every
+ field of that case in declaration order with the omitted ones filled in
+ as [Zero] — the
same ZII rule [Make] carries, and settled here for the same reason. It is
its own node rather than a [Make] over a synthesised struct because the
- value's *type* is the union and its payload is a byte blob the case is
+ value's *type* is the data type and its payload is a byte blob the case is
reinterpreted into; a backend that saw only [Make] would have to rederive
which of the two it was looking at. *)
| MakeCase of string * string * expr list
- (* One field of one case of a union value, by index. The case name is on the
+ (* One field of one case of a data type value, by index. The case name is on the
node because the payload is untyped bytes: [Field]'s index alone cannot
say which case struct the blob is being read as. [match] is the only thing
that proves the case, so this is only ever built under an arm that
@@ -243,7 +244,7 @@ type structure = { sname : string; fields : field list }
type variant = { vname : string; vfields : field list }
-type union = { uname : string; cases : variant list }
+type data = { dname : string; cases : variant list }
type fn = {
name : string;
@@ -300,7 +301,7 @@ type extern = {
type program = {
structs : structure list;
- unions : union list;
+ datas : data list;
globals : global list; (* in declaration order *)
externs : extern list;
fns : fn list;
@@ -316,10 +317,10 @@ type program = {
}
(* The declared position of a case, which is its tag, and the case itself. Tags
- are declaration order from zero, so an all-bytes-zero union is the first
+ are declaration order from zero, so an all-bytes-zero data type is the first
case with a zeroed payload — the same rule that makes an [Option]'s zero a
- [None], and the reason case order is part of a union's contract. *)
-let case_index (u : union) name =
+ [None], and the reason case order is part of a data type's contract. *)
+let case_index (u : data) name =
let rec go i = function
| [] -> None
| (c : variant) :: rest ->
diff --git a/lib/types.ml b/lib/types.ml
index 90f700f..be8e5aa 100644
--- a/lib/types.ml
+++ b/lib/types.ml
@@ -22,7 +22,7 @@ type t =
| String
| Unit (* the zero-sized type, not C's void *)
| Never (* return, exit, error: no value at all *)
- | Named of string (* a struct or union declared in the file *)
+ | Named of string (* a struct or data type, declared here *)
(* A C enum: an i32 at run time, but its own type, so a keyword at a call
site has something to resolve against and a plain integer does not fit. *)
| Enum of string
diff --git a/lib/x86.ml b/lib/x86.ml
index 9eecf2a..e69b6b3 100644
--- a/lib/x86.ml
+++ b/lib/x86.ml
@@ -429,16 +429,16 @@ let xorps b ~dst = rex b ~w:false ~r:dst ~x:0 ~m:dst; u8 b 0x0f; u8 b 0x57; modr
(* ── Types ───────────────────────────────────────────────────────────── *)
-(* [Emit.m] carries the struct and union tables [Emit.lay] reads. Built here
+(* [Emit.m] carries the struct and data type tables [Emit.lay] reads. Built here
rather than imported so that this module adds no line to [emit.ml]: the
record has no signature hiding it and every field it needs is inert. *)
let layout_ctx ~checks ~dev (p : Tast.program) : Emit.m =
- let structs = Hashtbl.create 16 and unions = Hashtbl.create 16 in
+ let structs = Hashtbl.create 16 and datas = Hashtbl.create 16 in
List.iter (fun (s : Tast.structure) -> Hashtbl.replace structs s.Tast.sname s)
p.Tast.structs;
- List.iter (fun (u : Tast.union) -> Hashtbl.replace unions u.Tast.uname u)
- p.Tast.unions;
- { Emit.out = Buffer.create 1; strs = Buffer.create 1; structs; unions;
+ List.iter (fun (u : Tast.data) -> Hashtbl.replace datas u.Tast.dname u)
+ p.Tast.datas;
+ { Emit.out = Buffer.create 1; strs = Buffer.create 1; structs; datas;
globals = Hashtbl.create 1; externs = Hashtbl.create 1; checks;
dev; known = (fun _ -> true); dbg = None; sanitize = false;
nstr = 0; nfi = 0 }
@@ -1068,7 +1068,7 @@ let imm_into f ~reg (n : int64) = movabs f.b ~dst:reg n
(* ── Struct layout, through [Emit] ───────────────────────────────────── *)
-let union_payload_off f (u : Tast.union) =
+let data_payload_off f (u : Tast.data) =
let size, align = Emit.payload_lay f.md u in
if size = 0 then 0
else
@@ -1089,22 +1089,22 @@ let field_offsets f (sn : string) =
in
offs
| None ->
- (* A union is a struct too, at this level: [emit.ml] lays it out as a tag
+ (* A data type is a struct too, at this level: [emit.ml] lays it out as a tag
and a payload blob, and the structural printer reads the tag as field 0
without unwrapping the value. *)
- (match Hashtbl.find_opt f.md.Emit.unions sn with
- | Some (u : Tast.union) -> [ 0; union_payload_off f u ]
+ (match Hashtbl.find_opt f.md.Emit.datas sn with
+ | Some (u : Tast.data) -> [ 0; data_payload_off f u ]
| None -> unsupported "no struct %s" sn)
-(* A union is { i32 tag, [k x iA] payload }, the same two fields [Emit.lay]
+(* A data type is { i32 tag, [k x iA] payload }, the same two fields [Emit.lay]
measures it as — so the payload's offset is whatever [lay_fields] puts the
- second one at, and not a rule spelled a second time here. A union whose
+ second one at, and not a rule spelled a second time here. A data type whose
cases are all payload-less is a bare tag and has no second field. *)
-let union_of f n =
- match Hashtbl.find_opt f.md.Emit.unions n with
+let data_of f n =
+ match Hashtbl.find_opt f.md.Emit.datas n with
| Some u -> u
- | None -> unsupported "no union %s" n
+ | None -> unsupported "no data type %s" n
(* The offsets of one case's fields inside the payload blob. The single place
in this backend that knows how a payload is read, so [match]'s binds,
@@ -1549,12 +1549,12 @@ and lower_at f (e : Tast.expr) (dst : loc) : unit =
jmp_lbl f.b f.retlbl;
lbl f.b lsome;
move f ~dst ~src:(shift src ov) payload
- | Tast.MakeCase (uname, case, fields) ->
- let u = union_of f uname in
+ | Tast.MakeCase (dname, case, fields) ->
+ let u = data_of f dname in
let i, c =
match Tast.case_index u case with
| Some (i, c) -> i, c
- | None -> unsupported "no case %s of %s" case uname
+ | None -> unsupported "no case %s of %s" case dname
in
(* Zeroed first: an omitted field is ZII and the payload blob is wider
than this case, so the bytes past its last field have to be something
@@ -1562,7 +1562,7 @@ and lower_at f (e : Tast.expr) (dst : loc) : unit =
zero_loc f dst (sizeof f.md t);
imm_into f ~reg:rax (Int64.of_int i);
store_int f.b ~src:rax ~mm:(lmem f dst ~scratch:r11) ~size:4;
- let poff = union_payload_off f u in
+ let poff = data_payload_off f u in
let offs = case_offsets f c in
List.iteri
(fun k (x : Tast.expr) ->
@@ -1956,25 +1956,25 @@ and lvalue f (e : Tast.expr) : loc =
| Tast.CaseField (target, case, i) -> case_field f target case i
| _ -> eval f e
-(* The address of one field of one case of a union value. Only ever reached
+(* The address of one field of one case of a data type value. Only ever reached
under an arm that proved the tag — [match] is the only thing that proves
it — or from the structural printer, which compares the same tag first. *)
and case_field f (target : Tast.expr) case i =
- let uname =
+ let dname =
match target.Tast.ty with
| Types.Named n -> n
| ty -> unsupported "case field of %s" (Types.to_string ty)
in
- let u = union_of f uname in
+ let u = data_of f dname in
let c =
match Tast.case_index u case with
| Some (_, c) -> c
- | None -> unsupported "no case %s of %s" case uname
+ | None -> unsupported "no case %s of %s" case dname
in
- shift (lvalue f target) (union_payload_off f u + List.nth (case_offsets f c) i)
+ shift (lvalue f target) (data_payload_off f u + List.nth (case_offsets f c) i)
(* [match]. The two subjects are the same shape and are read differently: an
- [Option] is an i8 tag and a payload at a known offset, a declared union is
+ [Option] is an i8 tag and a payload at a known offset, a declared data type is
an i32 tag and a blob the arm's case reinterprets. Everything past the tag
and the binds is shared, which is the arrangement [emit.ml] settled on for
the same reason. *)
@@ -1982,9 +1982,9 @@ and emit_match f (scrut : Tast.expr) (arms : Tast.arm list) dst t =
let base = lvalue f scrut in
let tag_size, tag_of, bind_at =
match scrut.Tast.ty with
- | Types.Named n when Hashtbl.mem f.md.Emit.unions n ->
- let u = union_of f n in
- let poff = union_payload_off f u in
+ | Types.Named n when Hashtbl.mem f.md.Emit.datas n ->
+ let u = data_of f n in
+ let poff = data_payload_off f u in
( 4,
(fun case ->
match Tast.case_index u case with
@@ -1998,7 +1998,7 @@ and emit_match f (scrut : Tast.expr) (arms : Tast.arm list) dst t =
| None -> unsupported "no case %s of %s" case n )
| Types.Option el ->
(* [lay_fields] puts the i8 tag at 0, so [base] is the tag's address the
- way it is for a union. *)
+ way it is for a data type. *)
let _, ov = option_lay f el in
( 1,
(fun case -> if String.equal case "Some" then 1 else 0),
diff --git a/syntax-sketch.flan b/syntax-sketch.flan
index 78e30fe..32462cb 100644
--- a/syntax-sketch.flan
+++ b/syntax-sketch.flan
@@ -49,7 +49,7 @@
hp i32
spr (Handle Texture)])
-(defunion Shape
+(defdata Shape
[(Circle [r f32])
(Rect [w f32 h f32])])
diff --git a/test/programs/unions.flan b/test/programs/datas.flan
similarity index 98%
rename from test/programs/unions.flan
rename to test/programs/datas.flan
index 070687c..bb23d13 100644
--- a/test/programs/unions.flan
+++ b/test/programs/datas.flan
@@ -9,7 +9,7 @@
;;;; string -- the three things a payload blob has to hold without disturbing
;;;; the alignment of any of them.
-(defunion Shape
+(defdata Shape
[Empty
(Dot [x f64 y f64])
(Rect [w i32 h i32])
@@ -39,7 +39,7 @@
;; A union that names itself through a pointer. check_finite refuses one that
;; contains itself by value -- the emitter would recurse forever laying it out
;; -- and this is the shape that works instead.
-(defunion Tree [Leaf (Node [l (Ptr Tree) n i32])])
+(defdata Tree [Leaf (Node [l (Ptr Tree) n i32])])
(defn depth [t (Ptr Tree)] i32
(match (deref t)
diff --git a/test/programs/dev-inspect.flan b/test/programs/dev-inspect.flan
index 753a8aa..748971a 100644
--- a/test/programs/dev-inspect.flan
+++ b/test/programs/dev-inspect.flan
@@ -17,7 +17,7 @@
(defstruct Point [x f32 y f32])
(defstruct Boom [why i32])
-(defunion Shape
+(defdata Shape
[Empty
(Dot [x f64 y f64])
(Rect [w i32 h i32])])
diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml
index 10d12f4..beb50ba 100644
--- a/test/test_acceptance.ml
+++ b/test/test_acceptance.ml
@@ -2141,10 +2141,10 @@ ERR@7 unexpected token: not the kind the caller was reading
shim_refuses "declare-c: an Option"
(v2 ^ "(declare-c maybe [] (Option Vector2) \"Maybe\")")
"which is a Flan shape and not a C one";
- shim_refuses "declare-c: a union"
- ("(defunion Shape [(Circle [r f32]) (Square [s f32])])\n\
+ shim_refuses "declare-c: a data type"
+ ("(defdata Shape [(Circle [r f32]) (Square [s f32])])\n\
(declare-c area [s Shape] f32 \"Area\")")
- "a union, and a Flan union has no C layout";
+ "a data type, and a Flan data type has no C layout";
shim_refuses "declare-c: a fixed array"
"(declare-c takes [xs [4 f32]] \"Takes\")"
"which C passes as a pointer and Flan as a value";
@@ -2299,19 +2299,19 @@ ERR@7 unexpected token: not the kind the caller was reading
refuses_src "a map used after it was moved"
"(defn main [] i32 (let [m (map-new i32 i32)] (free m) (put m 1 2)) 0)"
"cannot be used again";
- (* ── Union values ───────────────────────────────────────────
- defunion parsed and its shape was checked; naming the type and
+ (* ── Data type values ───────────────────────────────────────────
+ defdata parsed and its shape was checked; naming the type and
constructing a value were refused as milestone 6. The program covers a
case with no fields, a case wider than another, a case holding a
- string, a union in a struct, a union through a call in both directions,
+ string, a data type in a struct, a data type through a call in both directions,
ZII, reassignment and printing.
- -O0 as well, for the reason every aggregate here gets it: a union value
+ -O0 as well, for the reason every aggregate here gets it: a data type value
is built in an alloca and mem2reg is exactly what would hide a store to
the wrong half of it. And a dev build, because every body goes behind an
- indirection cell there and a union crosses one as a parameter and as a
+ indirection cell there and a data type crosses one as a parameter and as a
return value. *)
- let unions_out =
+ let datas_out =
"empty\ndot on the diagonal\ndot\nsquare\ntagged\nsquare\n\
32\n0\n-1\nin a cell\nempty\n30\nreassigned\n15\n\
Shape.Empty\n(Shape.Dot {.x 1.5 .y -2.5})\n\
@@ -2463,70 +2463,70 @@ ERR@7 unexpected token: not the kind the caller was reading
refuses "a macro that does not settle" "programs/macro-spin.flan"
"did not settle after";
- outputs "unions" "programs/unions.flan" unions_out;
- outputs ~opt:"-O0" "unions, -O0" "programs/unions.flan" unions_out;
- outputs ~dev:true "unions, dev" "programs/unions.flan" unions_out;
+ outputs "data types" "programs/datas.flan" datas_out;
+ outputs ~opt:"-O0" "data types, -O0" "programs/datas.flan" datas_out;
+ outputs ~dev:true "data types, dev" "programs/datas.flan" datas_out;
(* The refusals, each by name. The first is the diagnostics bug NEXT.md
listed and this lane fixed: a case name written as if it were a struct
reported "unknown struct A", because nothing in the environment could
tell a case from a misspelling. It can now. *)
- refuses_src "a union case written as a struct"
- "(defunion U [(A [x i32])])\n(defn main [] i32 (let [v (A {.x 1})] 0))"
- "A is a case of the union U";
- refuses_src "a union type used as a constructor"
- "(defunion U [(A [x i32])])\n(defn main [] i32 (let [v (U {.x 1})] 0))"
- "a union value names the case as well as the type";
+ refuses_src "a data type case written as a struct"
+ "(defdata U [(A [x i32])])\n(defn main [] i32 (let [v (A {.x 1})] 0))"
+ "A is a case of the data type U";
+ refuses_src "a data type type used as a constructor"
+ "(defdata U [(A [x i32])])\n(defn main [] i32 (let [v (U {.x 1})] 0))"
+ "a data type value names the case as well as the type";
refuses_src "a case with fields written bare"
- "(defunion U [(A [x i32])])\n(defn main [] i32 (let [v U.A] 0))"
+ "(defdata U [(A [x i32])])\n(defn main [] i32 (let [v U.A] 0))"
"has fields, so it needs them";
(* Exhaustiveness is refused rather than defaulted: a match that fell
through would have to produce a value of the match's type out of
- nothing, and the case a union grows tomorrow is the one a reader wants
+ nothing, and the case a data type grows tomorrow is the one a reader wants
to be told about today. *)
refuses_src "a match that misses a case"
- "(defunion U [A B C])\n\
+ "(defdata U [A B C])\n\
(defn main [] i32 (match U.A A 0 B 1))"
"this match is not exhaustive";
- refuses_src "a match arm naming a case the union does not have"
- "(defunion U [A B])\n(defn main [] i32 (match U.A A 0 B 1 Q 2))"
+ refuses_src "a match arm naming a case the data type does not have"
+ "(defdata U [A B])\n(defn main [] i32 (match U.A A 0 B 1 Q 2))"
"Q is not a case of U";
(* All of a case's fields or none: a pattern binding some of them would be
reading the wrong field the moment one is inserted above it. *)
refuses_src "a case pattern binding the wrong number of names"
- "(defunion U [(A [x i32 y i32])])\n\
+ "(defdata U [(A [x i32 y i32])])\n\
(defn f [u U] i32 (match u (A x) x))"
"binds every field, in declaration order";
refuses_src "two arms for one case"
- "(defunion U [A B])\n(defn main [] i32 (match U.A A 0 A 1 B 2))"
+ "(defdata U [A B])\n(defn main [] i32 (match U.A A 0 A 1 B 2))"
"two A arms";
- (* The declaration's own refusals. A union with no cases has no value, and
+ (* The declaration's own refusals. A data type with no cases has no value, and
a case owning a Vec is the refusal a struct field already carries, in
the same words and for the same reason. *)
- (* A union that contains itself by value has no finite size, and the
+ (* A data type that contains itself by value has no finite size, and the
emitter would recurse forever laying one out rather than failing. It is
refused where every other infinitely-sized type is, by the same walk,
- which already traversed a union's cases. Both shapes: direct, and two
- unions through each other. (Ptr T) breaks the cycle and is exercised in
+ which already traversed a data type's cases. Both shapes: direct, and two
+ data types through each other. (Ptr T) breaks the cycle and is exercised in
the program above -- it is the shape a Form has. *)
- refuses_src "a union that contains itself by value"
- "(defunion T [Leaf (Node [l T r T])])\n(defn f [t T] () 0)"
+ refuses_src "a data type that contains itself by value"
+ "(defdata T [Leaf (Node [l T r T])])\n(defn f [t T] () 0)"
"T contains itself by value";
- refuses_src "two unions that contain each other by value"
- "(defunion A [(X [b B])])\n(defunion B [(Y [a A])])\n(defn f [a A] () 0)"
+ refuses_src "two data types that contain each other by value"
+ "(defdata A [(X [b B])])\n(defdata B [(Y [a A])])\n(defn f [a A] () 0)"
"contains itself by value";
- refuses_src "a union with no cases"
- "(defunion U [])\n(defn f [u U] () 0)"
+ refuses_src "a data type with no cases"
+ "(defdata U [])\n(defn f [u U] () 0)"
"declares no cases";
- refuses_src "a union case that owns a Vec"
- "(defunion U [(A [v (Vec i32)])])\n(defn f [u U] () 0)"
+ refuses_src "a data type case that owns a Vec"
+ "(defdata U [(A [v (Vec i32)])])\n(defn f [u U] () 0)"
"which is move-only";
(* At the operation, not at the type: a struct key is decided by walking
its fields and the struct table is not necessarily complete while a
type is resolving, so both are answered where the hash and equality
- pair is emitted. A union reaches the same place. *)
- refuses_src "a union is not a map key"
- "(defunion U [A B])\n\
+ pair is emitted. A data type reaches the same place. *)
+ refuses_src "a data type is not a map key"
+ "(defdata U [A B])\n\
(defn f [m (Map U i32) k U] () (put m k 1))"
"the payload past the case in hand is indeterminate";
(* A global cannot hold a case, because writing one at link time means
@@ -2535,14 +2535,14 @@ ERR@7 unexpected token: not the kind the caller was reading
first declared case. Refused in the emitter, where the rest of the
same rule about a global's initialiser already lives, so the assertion
has to get that far rather than stopping at the checker. *)
- (let name = "a global initialised with a union case" in
+ (let name = "a global initialised with a data type case" in
let src =
- "(defunion U [A (B [x i32])])\n(defvar g U (U.B {.x 1}))\n\
+ "(defdata U [A (B [x i32])])\n(defvar g U (U.B {.x 1}))\n\
(defn main [] i32 0)"
in
match
Emit.program
- (Check.program (Parse.program (Reader.read_all ~file:"" src)))
+ (Check.program (Parse.program (Reader.read_all ~file:"" src)))
with
| _ ->
incr failures;
@@ -2554,26 +2554,26 @@ ERR@7 unexpected token: not the kind the caller was reading
Printf.printf "FAIL %s\n said: %S\n" name m
end);
(* uninit is an opt-out from ZII everywhere else and the bytes are just
- bytes. On a union they steer control flow: a tag no case names falls
+ bytes. On a data type they steer control flow: a tag no case names falls
past every comparison in a match into the block LLVM is entitled to
assume cannot be reached. *)
- refuses_src "uninit on a union global"
- "(defunion U [A B])\n(defvar g U uninit)\n(defn main [] i32 0)"
+ refuses_src "uninit on a data type global"
+ "(defdata U [A B])\n(defvar g U uninit)\n(defn main [] i32 0)"
"its tag steers every match";
- (* A union's fields belong to a case, so .field is not a read anyone can
+ (* A data type's fields belong to a case, so .field is not a read anyone can
do without having read the tag first. match is how one is opened. *)
- refuses_src "reading a field of a union directly"
- "(defunion U [(A [x i32])])\n(defn f [u U] i32 (.x u))"
+ refuses_src "reading a field of a data type directly"
+ "(defdata U [(A [x i32])])\n(defn f [u U] i32 (.x u))"
"reached by (match ...)";
(* And a zeroed one is fine, which is the other half of the same rule: it
is the first declared case, all bytes zero, and needs no encoder. *)
- (let name = "a zeroed union global" in
+ (let name = "a zeroed data type global" in
match
Emit.program
(Check.program
(Parse.program
- (Reader.read_all ~file:""
- "(defunion U [A (B [x i32])])\n(defvar g U)\n\
+ (Reader.read_all ~file:""
+ "(defdata U [A (B [x i32])])\n(defvar g U)\n\
(defn main [] i32 (match g A 0 (B x) x))")))
with
| _ -> ()
@@ -2875,7 +2875,7 @@ ERR@7 unexpected token: not the kind the caller was reading
(defstruct Board [tag u8 cells [4 P] here P edge (Ptr P) seen (Option i64)])\n\
(defn main [] i32 (let [b (Board {.tag 1})] (i32 (.tag b))))\n")
"Board" [ "tag"; "cells"; "here"; "edge"; "seen" ];
- (* A union, through the same oracle, because its layout is the one thing
+ (* A data type, through the same oracle, because its layout is the one thing
about it that has to be exactly right: the macro expander's Form has to
be the same bytes in the compiler and in the dlopened macro, and there
is nothing at run time that would notice a disagreement.
@@ -2887,15 +2887,15 @@ ERR@7 unexpected token: not the kind the caller was reading
and f64 for the alignment, so a tag of 4 padded to 8 and 16 bytes of
payload -- 24. A blob sized to the *first* case, or one aligned to the
tag, comes out at a different number and this says so. *)
- layout_case "DWARF offsets agree with LLVM: a union"
- ("(defunion U [Nil (Pair [a f64 b f64]) (One [n i32])])\n\
+ layout_case "DWARF offsets agree with LLVM: a data type"
+ ("(defdata U [Nil (Pair [a f64 b f64]) (One [n i32])])\n\
(defn main [] i32 (let [u U.Nil] (match u Nil 0 _ 1)))\n")
"U" [ "tag"; "payload" ];
- (* And the same union with a narrower widest case, so the payload is not a
+ (* And the same data type with a narrower widest case, so the payload is not a
constant this could have hard-coded: three i32 cases want 4-byte
alignment and 4 bytes of payload, which is 8 in total. *)
- layout_case "DWARF offsets agree with LLVM: a narrow union"
- ("(defunion N [(A [x i32]) (B [y i32]) (C [z i32])])\n\
+ layout_case "DWARF offsets agree with LLVM: a narrow data type"
+ ("(defdata N [(A [x i32]) (B [y i32]) (C [z i32])])\n\
(defn main [] i32 (let [n (N.A {.x 3})] (match n (A x) x _ 1)))\n")
"N" [ "tag"; "payload" ];
@@ -3254,7 +3254,7 @@ ERR@7 unexpected token: not the kind the caller was reading
(match
Build.executable
~opts:{ Build.default with debug = true; target = Some "wasm32-wasi" }
- { Tast.structs = []; unions = []; globals = []; externs = []; fns = [];
+ { Tast.structs = []; datas = []; globals = []; externs = []; fns = [];
cshim = [] }
~out:(Filename.concat scratch "flan-dbg-wasm")
with
diff --git a/test/test_dev.ml b/test/test_dev.ml
index d549100..57caf4e 100644
--- a/test/test_dev.ml
+++ b/test/test_dev.ml
@@ -325,14 +325,14 @@ let () =
end;
let r =
request c
- "(:op \"eval\" :code \"(defunion Shape [(Circle [r f32])])\" :file \"/tmp/buf.flan\")"
+ "(:op \"eval\" :code \"(defdata Shape [(Circle [r f32])])\" :file \"/tmp/buf.flan\")"
in
- if status r <> "ok" then fail "a new union: %s" (refusal r)
+ if status r <> "ok" then fail "a new data type: %s" (refusal r)
else begin
let r = request c "(:op \"layout\" :type \"Shape\")" in
- if status r <> "error" then fail "a union answered a struct layout"
- else if not (contains (refusal r) "is a union") then
- fail "a union is refused as: %s" (refusal r)
+ if status r <> "error" then fail "a data type answered a struct layout"
+ else if not (contains (refusal r) "is a data type") then
+ fail "a data type is refused as: %s" (refusal r)
end;
(* The identity rule, and the case NEXT.md named: a second [Blob] typed
@@ -1154,7 +1154,7 @@ let () =
It also carries the two shapes an expression cannot reach at all: an
option's payload, which no accessor form in the language names, and a
- union case's field, whose offset depends on which case the value is
+ data type case's field, whose offset depends on which case the value is
in. *)
let isock = tmp "inspect.sock" and iout = tmp "inspect.out" in
(try Sys.remove isock with Sys_error _ -> ());
@@ -1289,7 +1289,7 @@ let () =
[ ("mark", "(\"nope\")", "no field called nope");
("mark", "(some)", "not an option");
("xs", "(9)", "past the end");
- (* A union field without its case: the payload's offset depends
+ (* A data type field without its case: the payload's offset depends
on the case, so guessing one that two cases share would read
one case's layout over another's payload. *)
("s", "(\"w\")", "name the case") ]
diff --git a/test/test_flan.ml b/test/test_flan.ml
index bbf2c74..516e65c 100644
--- a/test/test_flan.ml
+++ b/test/test_flan.ml
@@ -470,6 +470,24 @@ let () =
parse_rejects "defmacro in expression position" "(defn f [] () (defmacro m [] 1))"
~needle:"top-level declaration";
+ (* The tagged sum is [defdata] now. The old spelling is refused by name
+ rather than aliased, because the name is reserved for a type with
+ different semantics — a file that kept [defunion] must be made to say
+ which of the two it means instead of being quietly given one of them. *)
+ parse_rejects "the old defunion spelling"
+ "(defunion Shape [(Circle [r f32]) (Square [s f32])])"
+ ~needle:"defunion is now defdata";
+ (* The shape that would otherwise parse: two bare case names read as one
+ field of a type. Same refusal, because the arm dispatches on the head and
+ never looks at what follows it. *)
+ parse_rejects "the old defunion spelling with payload-less cases"
+ "(defunion U [A B])"
+ ~needle:"defunion is now defdata";
+ (match read "(defunion U [A B])" |> Parse.program with
+ | _ -> check "the old spelling has a kind" false
+ | exception Loc.Error { Loc.kind; _ } ->
+ check "the old spelling has a kind" (kind = "parse/defunion-renamed"));
+
(* The return type is not optional. A void function writes (), and the
refusal says so rather than leaving someone to find it in a grammar. *)
parse_rejects "defn with no return type" "(defn f [] (g))"
@@ -1497,11 +1515,11 @@ let () =
"(defenum K [lo 0 hi 1])\n(defn f [k K] i32 (match k lo 1 hi 2))"
~needle:"match over the enum K is not implemented";
(* The old message blamed milestone 2, which was never the reason, and the
- milestone has since arrived: match now works over a declared union as
+ milestone has since arrived: match now works over a declared data type as
well, so the message names both subjects and no milestone. *)
rejects_check "match over something that is neither"
"(defn f [n i32] i32 (match n _ 2))"
- ~needle:"match works on an Option or a union, not on i32";
+ ~needle:"match works on an Option or a data type, not on i32";
(* A destructuring pattern in an arm's binds is a name position like any
other. *)
rejects_check "a pattern inside a match arm's binds"
diff --git a/test/test_valgrind.ml b/test/test_valgrind.ml
index 523f42f..cf70ed7 100644
--- a/test/test_valgrind.ml
+++ b/test/test_valgrind.ml
@@ -240,7 +240,7 @@ let corpus =
"programs/stale-region.flan", [];
"programs/string-of-bytes.flan", [];
"programs/text.flan", [];
- "programs/unions.flan", [];
+ "programs/datas.flan", [];
"programs/unit-main.flan", [];
"programs/utf8.flan", [];
"programs/values.flan", [];
@@ -271,7 +271,7 @@ let unchecked_subset =
"programs/slices.flan", [];
"programs/slurp.flan", [];
"programs/text.flan", [];
- "programs/unions.flan", [];
+ "programs/datas.flan", [];
"programs/utf8.flan", [];
"programs/vec.flan", [];
"../calc-me.flan", [ "1 + 2 * (3 - 0.5) / 2" ] ]
diff --git a/web/index.html b/web/index.html
index 3ac93c3..dc9bebd 100644
--- a/web/index.html
+++ b/web/index.html
@@ -522,7 +522,7 @@ notation reads as exactly one data item.
Allocator | an opaque builtin: a proc, its data and a capability set | a pointer to that |
$t | a type variable — see generics | whatever it is instantiated at |
| a struct | value type | fields in declaration order |
-| a union | defunion, matched by case | tag + the widest payload |
+| a tagged data type | defdata, matched by case | tag + the widest payload |
| an enum | its own type in the checker | i32 |
() | one value, zero size | empty |
Never | fits anywhere; nothing has it | empty |
@@ -762,7 +762,7 @@ as first-even does above.
(Option T) is how absence is spelled: a lookup miss, an empty
collection, the end of a stream. match works on an Option and
-on a defunion, and on nothing else. some unwraps
+on a defdata, and on nothing else. some unwraps
Some and early-returns None from the enclosing function.
(defconst nums [4 i32] [4 8 15 16])
@@ -2108,7 +2108,7 @@ disagree with the first.