From 271411b3e226a52882a5fa678ef0119928f55ef3 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Fri, 26 Jun 2026 21:25:10 -0400 Subject: [PATCH 1/3] Fix conj on a lazy-seq, add lazy-seq interop regression rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rest = more() change made (rest coll) return a jolt-lazyseq, so the very common (conj (rest xs) y) hit jolt-conj1's base case, which doesn't recognize a lazyseq, and threw "conj: unsupported collection" (caught by core.match's seq-pattern compiler). conj on a lazy-seq now prepends like conj on any seq. The corpus had no row exercising a collection op on a rest-derived seq, so the class slipped past the gate; add a seqs/lazy-seq-interop suite (conj/into/first/ count/nth/reduce/map/filter/apply/cons/=/empty?/seq over (rest …) and lazy-seq), all JVM-certified. --- host/chez/lazy-bridge.ss | 7 +++++++ test/chez/corpus.edn | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/host/chez/lazy-bridge.ss b/host/chez/lazy-bridge.ss index acd7dd9..76e0043 100644 --- a/host/chez/lazy-bridge.ss +++ b/host/chez/lazy-bridge.ss @@ -49,6 +49,13 @@ (cseq-lazy x (lambda () (force-lazyseq coll))) (%ls-cons x coll)))) +;; (conj lazyseq x): conj onto a seq prepends, like any seq — (conj (rest xs) y). +;; rest returns a lazyseq, so this is a common path; without it conj reports the +;; lazyseq as an "unsupported collection". +(define %ls-conj1 jolt-conj1) +(set! jolt-conj1 (lambda (coll x) + (if (jolt-lazyseq? coll) (jolt-cons x coll) (%ls-conj1 coll x)))) + ;; A lazyseq is a NEW value type, so the dispatchers that DON'T route through ;; jolt-seq must learn it or a raw (unrealized) lazyseq escapes — e.g. the corpus ;; compares (= [1 3 5] (take-nth 2 …)) against the raw lazyseq, and jolt=2 would diff --git a/test/chez/corpus.edn b/test/chez/corpus.edn index 0ea113d..67b70b7 100644 --- a/test/chez/corpus.edn +++ b/test/chez/corpus.edn @@ -3278,4 +3278,19 @@ {:suite "interop / uri equality" :label "URIs are value-equal and usable as set members" :expected "[true true]" :actual "[(= (java.net.URI. \"/\") (java.net.URI. \"/\")) (= #{(java.net.URI. \"/\")} #{(java.net.URI. \"/\")})]"} {:suite "stdlib / clojure.walk" :label "macroexpand-all expands a form" :expected "true" :actual "(do (require (quote clojure.walk)) (seq? (clojure.walk/macroexpand-all (quote (when true 1)))))"} {:suite "regex / re-seq zero-width" :label "a zero-width match advances by one without repeating" :expected "[\"a\" \"\" \"a\" \"\"]" :actual "(vec (re-seq #\"a*\" \"aba\"))"} + {:suite "seqs / lazy-seq interop" :label "conj onto a rest-derived seq prepends" :expected "true" :actual "(= [9 2 3] (vec (conj (rest [1 2 3]) 9)))"} + {:suite "seqs / lazy-seq interop" :label "conj onto a lazy-seq prepends" :expected "true" :actual "(= [9 1 2] (vec (conj (lazy-seq (list 1 2)) 9)))"} + {:suite "seqs / lazy-seq interop" :label "into over a conj'd rest-seq" :expected "true" :actual "(= [0 2 3] (into [] (conj (rest [1 2 3]) 0)))"} + {:suite "seqs / lazy-seq interop" :label "first of a conj'd rest-seq" :expected "true" :actual "(= 9 (first (conj (rest [1 2 3]) 9)))"} + {:suite "seqs / lazy-seq interop" :label "count of a conj'd rest-seq" :expected "true" :actual "(= 3 (count (conj (rest [1 2 3]) 9)))"} + {:suite "seqs / lazy-seq interop" :label "nth into a rest-seq" :expected "true" :actual "(= 3 (nth (rest [1 2 3]) 1))"} + {:suite "seqs / lazy-seq interop" :label "reduce over a rest-seq" :expected "true" :actual "(= 5 (reduce + (rest [1 2 3])))"} + {:suite "seqs / lazy-seq interop" :label "a rest-seq equals the literal tail" :expected "true" :actual "(= (quote (2 3)) (rest [1 2 3]))"} + {:suite "seqs / lazy-seq interop" :label "map over a rest-seq" :expected "true" :actual "(= [3 4] (vec (map inc (rest [1 2 3]))))"} + {:suite "seqs / lazy-seq interop" :label "filter over a rest-seq" :expected "true" :actual "(= [3] (vec (filter odd? (rest [1 2 3 4]))))"} + {:suite "seqs / lazy-seq interop" :label "apply over a rest-seq" :expected "true" :actual "(= 5 (apply + (rest [1 2 3])))"} + {:suite "seqs / lazy-seq interop" :label "seq of an empty rest is nil" :expected "true" :actual "(nil? (seq (rest [1])))"} + {:suite "seqs / lazy-seq interop" :label "empty? of an empty rest" :expected "true" :actual "(empty? (rest [1]))"} + {:suite "seqs / lazy-seq interop" :label "cons onto a rest-seq" :expected "true" :actual "(= [0 2 3] (vec (cons 0 (rest [1 2 3]))))"} + {:suite "seqs / lazy-seq interop" :label "into a set from a rest-seq" :expected "true" :actual "(= #{2 3} (into #{} (rest [1 2 3])))"} ] From 3dc5de91e59c932a852b2caab7f8ae5b25eb62cd Mon Sep 17 00:00:00 2001 From: Yogthos Date: Fri, 26 Jun 2026 21:36:45 -0400 Subject: [PATCH 2/3] Fix map? for a deftype implementing IPersistentMap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deftype-is-not-a-map change (#245) gated map? on jrec-record?, so only a defrecord was map?. But a deftype that implements clojure.lang.IPersistentMap is map? on the JVM — clojure.core.cache's caches are exactly that, and its TTL factory asserts (map? base) on an LRUCache passed as the base (its suite went 1314 -> 2 errors). map? now also covers a deftype whose without/dissoc method is registered — the IPersistentMap-distinctive op a vector or set lacks. An opaque deftype (RawString) stays non-map?; a defrecord stays both. Guards added to unit.edn (jolt-side: a full IPersistentMap impl will not compile on the JVM corpus oracle). --- host/chez/records.ss | 10 +++++++++- test/chez/unit.edn | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/host/chez/records.ss b/host/chez/records.ss index 00b0a95..66fdc57 100644 --- a/host/chez/records.ss +++ b/host/chez/records.ss @@ -58,6 +58,14 @@ (find-method-any-protocol tag "valAt") (find-method-any-protocol tag "cons")))) #t)) +;; a jrec that is map? — a record, or a deftype implementing clojure.lang +;; .IPersistentMap (clojure.core.cache's caches do). `without` (dissoc) is the +;; map-distinctive method: vectors/sets implement Associative/ILookup but not it. +(define (jrec-maplike? x) + (and (jrec? x) + (or (jrec-record? x) + (find-method-any-protocol (jrec-tag x) "without")) + #t)) (define jolt-deftype-kw (keyword "jolt" "deftype")) ;; unique present-vs-absent sentinel for extension-map lookups (so a present nil ;; in the extension map is distinguished from a genuine miss). @@ -399,7 +407,7 @@ ;; deftype is not. coll? additionally covers a deftype implementing a collection ;; interface. predicates.ss vars hold a snapshot, so re-def-var! after extending. (define %r-jolt-map? jolt-map?) -(set! jolt-map? (lambda (x) (or (jrec-record? x) (%r-jolt-map? x)))) +(set! jolt-map? (lambda (x) (or (jrec-maplike? x) (%r-jolt-map? x)))) (def-var! "clojure.core" "map?" jolt-map?) (def-var! "clojure.core" "coll?" (lambda (x) (or (jrec-collection? x) (jolt-coll-pred? x)))) diff --git a/test/chez/unit.edn b/test/chez/unit.edn index cb1a1ec..5e85d49 100644 --- a/test/chez/unit.edn +++ b/test/chez/unit.edn @@ -563,4 +563,8 @@ {:suite "bytes" :expr "(String. (byte-array [104 105]) \"UTF-8\")" :expected "hi"} {:suite "bytes" :expr "(int (first (String. (byte-array [200]) \"ISO-8859-1\")))" :expected "200"} {:suite "bytes" :expr "(String. (.getBytes \"round\" \"UTF-8\") \"UTF-8\")" :expected "round"} + {:suite "deftype-map" :expr "(do (deftype Mp [m] clojure.lang.IPersistentMap (without [_ k] m)) [(map? (->Mp 1)) (record? (->Mp 1))])" :expected "[true false]"} + {:suite "deftype-map" :expr "(do (deftype Op [s]) [(map? (->Op 1)) (record? (->Op 1)) (coll? (->Op 1))])" :expected "[false false false]"} + {:suite "deftype-map" :expr "(do (deftype Lc [xs] clojure.lang.Counted (count [_] (count xs)) clojure.lang.Seqable (seq [_] (seq xs))) [(coll? (->Lc [1 2])) (count (->Lc [1 2])) (vec (seq (->Lc [1 2])))])" :expected "[true 2 [1 2]]"} + {:suite "deftype-map" :expr "(do (defrecord Dr [a b]) [(map? (->Dr 1 2)) (record? (->Dr 1 2)) (coll? (->Dr 1 2))])" :expected "[true true true]"} ] From b74dbfd2f0688cd9f48df04ee2e2cb219c28fbb0 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Fri, 26 Jun 2026 22:05:21 -0400 Subject: [PATCH 3/3] Symbols are IFn (invoke as a map lookup) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ('sym coll) / ('sym coll default) now do (get coll 'sym ...), like keywords — a symbol is IFn on the JVM. jolt threw "cannot be cast to clojure.lang.IFn". Pre-existing gap (not a regression), surfaced by honeysql's :checking mode, which does ('where dsl) to look up a clause. honeysql 623/13/8 -> 635/8/1. Corpus rows added. --- host/chez/seq.ss | 1 + test/chez/corpus.edn | 3 +++ 2 files changed, 4 insertions(+) diff --git a/host/chez/seq.ss b/host/chez/seq.ss index 7ac020a..98401ad 100644 --- a/host/chez/seq.ss +++ b/host/chez/seq.ss @@ -161,6 +161,7 @@ (cond ((procedure? f) (apply f args)) ((keyword? f) (apply jolt-get (car args) f (cdr args))) ; (:k m [d]) -> (get m :k [d]) + ((jolt-symbol? f) (apply jolt-get (car args) f (cdr args))) ; ('s m [d]) -> (get m 's [d]) ((jolt-coll? f) (apply jolt-get f args)) ; (coll k [d]) -> (get coll k [d]) ((jolt-transient? f) (apply jolt-get f args)) ; a transient vec/map/set is callable on the JVM ;; a record/reify implementing clojure.lang.IFn is callable: dispatch to its diff --git a/test/chez/corpus.edn b/test/chez/corpus.edn index 67b70b7..c880e4b 100644 --- a/test/chez/corpus.edn +++ b/test/chez/corpus.edn @@ -3293,4 +3293,7 @@ {:suite "seqs / lazy-seq interop" :label "empty? of an empty rest" :expected "true" :actual "(empty? (rest [1]))"} {:suite "seqs / lazy-seq interop" :label "cons onto a rest-seq" :expected "true" :actual "(= [0 2 3] (vec (cons 0 (rest [1 2 3]))))"} {:suite "seqs / lazy-seq interop" :label "into a set from a rest-seq" :expected "true" :actual "(= #{2 3} (into #{} (rest [1 2 3])))"} + {:suite "ifn / symbol as fn" :label "a symbol invokes as a map lookup" :expected "true" :actual "(= 5 ('a {(quote a) 5}))"} + {:suite "ifn / symbol as fn" :label "a symbol fn takes a not-found default" :expected "true" :actual "(= :d ('a {} :d))"} + {:suite "ifn / symbol as fn" :label "a symbol works as a mapping fn" :expected "true" :actual "(= [1 2] (vec (map (quote k) [{(quote k) 1} {(quote k) 2}])))"} ]