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