From ea2446110775573b924275202e54dfd10cdf448b Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 16:34:25 +0700 Subject: [PATCH] Cover a dev build and a map that leaves its let MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two gaps nothing in the suite reached. A dev build, because the hash and equality pair emitted for a struct key is a function nobody wrote, and the only other inhabitant of the lifted list — a handler-bind clause — carries a parent this one cannot: the pair is shared by every function that maps that key type, so it has no single parent. A dev build puts every body behind an indirection cell and is the build that would notice. It does not; maps.flan answers the same nineteen ways at --dev as it does at -O2 and -O0. And a map crossing a function boundary in both directions. Everything else in the file lives and dies inside one let, so nothing would have noticed if the 48-byte header travelled wrongly by value while every runtime operation takes its address. Returning one and passing one are both moves, which is the rule a Vec already follows — verified against a Vec rather than assumed, since a refusal that fired for the wrong reason would look the same. has-key? is flagged in BUILT.md as what it is: an addition, not something spec-memory.md names. --- BUILT.md | 7 ++++++- test/programs/maps.flan | 23 +++++++++++++++++++++++ test/test_acceptance.ml | 8 +++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/BUILT.md b/BUILT.md index bf285ba..1500d01 100644 --- a/BUILT.md +++ b/BUILT.md @@ -1719,9 +1719,14 @@ calls per field, which has no channel to hand on. | `(map-new)` `(map-new K V)` `(map-new a)` `(map-new K V a)` | a new map; the pair may be omitted where the context says | | `(put m k v)` | upsert, `Unit` | | `(get m k)` | `(Option V)` — absence is `None` | -| `(has-key? m k)` | `bool`, copying no value | +| `(has-key? m k)` | `bool`, copying no value — **an addition; the spec does not name it** | | `(len m)` `(reserve m n)` `(clone m)` `(clone m a)` `(free m)` | extended, not duplicated | +`has-key?` is **not in `spec-memory.md`** and is an addition, flagged because everything else here is the spec's. +`(get m k)` answers the same question, but through an `Option` the caller then has to match, and the common use is a +condition. It copies no value, which is also why it is not just `get` with the result thrown away. The `?` suffix +follows `can-free?`. + `len`, `reserve`, `clone` and `free` were **extended rather than given map-shaped names of their own**, which is what `at` and `len` already did for `Vec`: one question, one word. `reserve`'s `n` is entries, not slots — the runtime sizes the block so `n` still sits under the load factor, which is the only reading of "room for n" that does not reallocate diff --git a/test/programs/maps.flan b/test/programs/maps.flan index 31915c7..0b917a8 100644 --- a/test/programs/maps.flan +++ b/test/programs/maps.flan @@ -8,6 +8,25 @@ (defstruct Named [tag string n i32]) (defenum Suit [hearts 0 spades 1 clubs 2]) +;;;; (8) A map crosses a function boundary in both directions. Returning one +;;;; and passing one are both *moves* — the same rule a Vec follows, so the +;;;; binding is dead afterwards — and the 48-byte header travels by value while +;;;; every runtime operation takes its address. Nothing else in this file +;;;; leaves a single let, so nothing else would notice if it did not. +(defstruct Cell2 [x i32 y i32]) + +(defn make-grid [] (Map Cell2 i32) + (let [m (map-new Cell2 i32)] + (put m (Cell2 {.x 1 .y 2}) 12) + (put m (Cell2 {.x 3 .y 4}) 34) + m)) + +;; Takes the map, which is a move: this owns it now, and frees it. +(defn consume-grid [m (Map Cell2 i32)] i32 + (let [n (len m)] + (free m) + n)) + (defn main [] i32 ;; (1) An integer key past several grows. The map starts at 8 slots, so 2000 ;; entries is eight reallocations, and every one of them rehashes against a @@ -103,4 +122,8 @@ (print (len t)) (println "") ; 500 (match (get t 499) (Some v) (do (print v) (println "")) None (println "?")))) ; 998 (free-all ar)) + + (let [g (make-grid)] + (print (len g)) (println "") ; 2 + (print (consume-grid g)) (println "")) ; 2 0) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 19cc859..7f2801b 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -1567,10 +1567,16 @@ ERR@7 unexpected token: not the kind the caller was reading 2000 entries is eight grows and then every one of them read back. *) let maps_out = "2000\n0\n1600\n709\ntrue\nfalse\n3\n20\nfalse\n2\n3\nfalse\n\ - 100\n999\n2\n1\n3\n500\n998\n" + 100\n999\n2\n1\n3\n500\n998\n2\n2\n" in outputs "maps" "programs/maps.flan" maps_out; outputs ~opt:"-O0" "maps, -O0" "programs/maps.flan" maps_out; + (* A dev build, because the hash and equality pair emitted for a struct key + is a function nobody wrote and the only other inhabitant of that list — + a lifted handler clause — carries a parent this one cannot: the pair is + shared by every function that maps that key type. A dev build puts every + body behind an indirection cell, so it is the build that would notice. *) + outputs ~dev:true "maps, dev" "programs/maps.flan" maps_out; (* The allocation-failure rule is one rule over every allocating operation, so it has to hold for map-new, put, reserve and clone as it does for the