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.

Allocatoran opaque builtin: a proc, its data and a capability seta pointer to that $ta type variable — see genericswhatever it is instantiated at a structvalue typefields in declaration order -a uniondefunion, matched by casetag + the widest payload +a tagged data typedefdata, matched by casetag + the widest payload an enumits own type in the checkeri32 ()one value, zero sizeempty Neverfits anywhere; nothing has itempty @@ -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.