core: fix Option-A suite regressions — nil lazy elements + non-seqable input

Running clojure-test-suite surfaced an Option A regression (3971 -> 3957),
isolated to two root causes, both around lazy seqs:

1. nil first element wrongly read as end-of-seq. core-empty?, core-seq, and
   core-reverse tested a lazy seq's emptiness with (nil? (ls-first coll)) — but a
   lazy element may legitimately be nil. With Option A's lazy `drop`, the `case`
   macro's (empty? (drop 2 clauses)) hit a nil-first lazy seq at the `nil`
   case-constant and collapsed the rest of the case (incl :default) to nil —
   breaking 14 case.cljc assertions. Now they realize one cell (seq-done?-style)
   instead of trusting ls-first.

2. lazy transformer over a non-seqable silently yielded empty. The eager path
   threw (realize-for-iteration on a char/number errors); Option A's lazy-from
   returned nil, so (first (remove nil? \a)) gave nil where Clojure throws.
   lazy-from now rejects non-seqable scalars (number/boolean/keyword/char/symbol)
   with "Don't know how to create ISeq from: …".

Result: suite 3971 -> 3981 pass (net gain), clean files 45 -> 66 (Option A makes
seq?/vector? match Clojure across many cross-dialect files). Baseline raised.

Gate: conformance 258x3 (+5 regression guards), lazy-infinite 44/44, suite
3981/66, fixpoint, self-host, specs+unit green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yogthos 2026-06-08 18:50:36 -04:00
parent a68210e440
commit 9a9de08047
3 changed files with 40 additions and 10 deletions

View file

@ -32,9 +32,15 @@
# which several suite tests assert. Runs read 3927 consistently, occasionally 3926
# when a timeout-prone test (of the 9 that can time out) doesn't finish; floor at
# the consistent-minus-one 3926.
(def baseline-pass 3971)
# Raised 3971 -> 3981 with Option A full laziness (jolt-fng): transformers return
# lazy seqs, lazy interleave stops timing out, and the lazy-seq nil-element +
# non-seqable-input fixes (case/seq/reverse/empty? over nil-first lazy seqs;
# lazy-from throws on non-seqable like Clojure) recovered + extended the suite.
# clean files 45 -> 66 (Option A makes seq?/vector? results match Clojure across
# many cross-dialect files). Stable across runs.
(def baseline-pass 3981)
# A file is "clean" when it ran with zero failures AND zero errors.
(def baseline-clean-files 45)
(def baseline-clean-files 66)
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s;
# this only fires on infinite-sequence hangs.
(def per-file-timeout 6)

View file

@ -157,6 +157,15 @@
["partition-by odd?" "(quote ((1 1) (2) (3 3)))" "(partition-by odd? [1 1 2 3 3])"]
["reductions inf" "(quote (0 1 3 6))" "(take 4 (reductions + (range)))"]
["tree-seq strict" "10" "(reduce + 0 (filter (complement coll?) (tree-seq coll? seq [1 [2 [3 4]]])))"]
# nil/collection case-constants past the point where Option A's lazy `drop`
# made the case macro's (empty? (drop 2 cls)) hit a nil-first lazy seq.
["case nil + default" "[:nilr :def]" "(let [f (fn [x] (case x 1 :one nil :nilr :def))] [(f nil) (f 9)])"]
["case collection consts" "[:v :m :s]" "(let [f (fn [x] (case x [1 2] :v {:a 1} :m #{3} :s :def))] [(f [1 2]) (f {:a 1}) (f #{3})])"]
# a lazy seq whose first element is nil is non-empty (seq/empty?/reverse)
["seq of nil-first" "true" "(boolean (seq (cons nil (list 1))))"]
["reverse nil elem" "[2 nil 1]" "(vec (reverse (list 1 nil 2)))"]
# lazy transformer over a non-seqable scalar throws (matches Clojure)
["map non-seqable throws" "true" "(try (doall (map inc 5)) false (catch Throwable _ true))"]
["keep-indexed" "(quote (:b :d))" "(keep-indexed (fn [i x] (if (odd? i) x)) [:a :b :c :d])"]
["map-indexed" "(quote ([0 :a] [1 :b]))" "(map-indexed (fn [i x] [i x]) [:a :b])"]
["trampoline" ":done" "(do (defn a [n] (if (zero? n) :done (fn [] (a (dec n))))) (trampoline a 5))"]