Skip to content

Commit 595761c

Browse files
leifericfclaude
andcommitted
sequences: seq_iter_init walks records via prim_seq's kv decomposition
(into {} record) returned {} because seq_iter_init's switch fell through the default (immediately-done) case for MINO_RECORD. prim_seq had a record case that built a [k v] cons list in declared order, but every downstream consumer (into, reduce, transduce, etc.) talks to seq_iter_init instead of prim_seq directly. seq_iter_init now calls prim_seq up-front when given a record, then proceeds as if walking the resulting cons list. (into {} record), (into [] record), and (reduce conj #{} record) all work as expected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 33b4125 commit 595761c

4 files changed

Lines changed: 51 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# Changelog
22

3+
## v0.255.25 — Fix: `seq_iter_init` walks records as kv pairs
4+
5+
`(into {} record)` returned `{}` instead of `{:x 1 :y 2}`. The
6+
iterator that backs `into`, `reduce`, `transduce`, and most other
7+
seq aggregates fell through to the default (immediately-done) case
8+
for `MINO_RECORD`. `prim_seq` itself had a record case that built a
9+
`[k v]` cons list in declared-field-then-ext order, but the
10+
iterator didn't route through it.
11+
12+
`seq_iter_init` now routes `MINO_RECORD` through `prim_seq` up-front
13+
so every downstream consumer walks records as a seq of `[k v]`
14+
pairs. After this:
15+
16+
```clojure
17+
(into {} (->Point 1 2)) ; => {:x 1 :y 2}
18+
(into [] (->Point 1 2)) ; => [[:x 1] [:y 2]]
19+
(reduce conj #{} (->Point 1 2)) ; => #{[:x 1] [:y 2]}
20+
```
21+
22+
Regression in `tests/records_test.clj` (`record-seq-is-iterable`).
23+
324
## v0.255.24 — Fix: Vector destructuring of lazy / chunked seqs
425

526
`(let [[a b c] (range 3)] [a b c])` returned `[nil nil nil]` instead

src/mino.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
*/
2929
#define MINO_VERSION_MAJOR 0
3030
#define MINO_VERSION_MINOR 255
31-
#define MINO_VERSION_PATCH 24
31+
#define MINO_VERSION_PATCH 25
3232

3333
/*
3434
* Human-readable version string of the *linked* runtime, e.g. "0.48.0".

src/prim/sequences.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,18 @@ void seq_iter_init(mino_state_t *S, seq_iter_t *it, const mino_val_t *coll)
330330
(mino_type_of(coll) == MINO_SORTED_MAP || mino_type_of(coll) == MINO_SORTED_SET)) {
331331
coll = sorted_seq(S, coll);
332332
}
333+
/* Records: route through prim_seq which builds a cons list of
334+
* [k v] pairs in declared-field-then-ext-insertion order. Without
335+
* this, seq_iter_done returns true immediately for MINO_RECORD
336+
* (default case), so `(into {} record)` returned `{}` and any
337+
* other seq-based aggregate over a record came back empty. */
338+
if (coll != NULL && mino_type_of(coll) == MINO_RECORD) {
339+
mino_val_t *args = mino_cons(S, (mino_val_t *)coll, mino_nil(S));
340+
coll = prim_seq(S, args, NULL);
341+
if (coll != NULL && mino_type_of(coll) == MINO_LAZY) {
342+
coll = lazy_force(S, (mino_val_t *)coll);
343+
}
344+
}
333345
it->coll = coll;
334346
it->idx = 0;
335347
it->cons_p = (coll != NULL && mino_type_of(coll) == MINO_CONS) ? coll : NULL;

tests/records_test.clj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@
109109
(is (satisfies? IGreet g))
110110
(is (instance? Greeter g))))
111111

112+
(deftest record-seq-is-iterable
113+
;; Regression: seq_iter_init didn't handle MINO_RECORD, so any seq-
114+
;; iterator-based aggregate over a record came back empty.
115+
;; (into {} record) returned {} and (reduce kv-fn {} record) was
116+
;; a no-op. (seq record) itself worked (prim_seq has a record case)
117+
;; -- the gap was in the iterator. seq_iter_init now routes records
118+
;; through prim_seq up-front so every downstream consumer (into,
119+
;; reduce, transduce, etc.) walks records as a seq of [k v] pairs.
120+
(defrecord PointForInto [x y])
121+
(let [p (->PointForInto 1 2)]
122+
(testing "(into {} record) collects fields as map entries"
123+
(is (= {:x 1 :y 2} (into {} p))))
124+
(testing "(into [] record) collects [k v] pairs in declared order"
125+
(is (= [[:x 1] [:y 2]] (into [] p))))
126+
(testing "(reduce conj #{} record) walks the kv pairs"
127+
(is (= #{[:x 1] [:y 2]} (reduce conj #{} p))))))
128+
112129
(deftest defrecord-inline-method-binds-fields
113130
;; Real Clojure binds each declared field as a local inside any
114131
;; inline protocol method body, so the body can write the field

0 commit comments

Comments
 (0)