map/filter/remove/take/drop/concat/take-while/drop-while/mapcat/partition
built an eager-headed cseq: the first element (and the fn application) ran
at construction, so a side-effecting (map f coll) fired f immediately and
(class (map …)) was PersistentList instead of LazySeq. This diverged from
Clojure, which wraps the whole body in lazy-seq. It went unnoticed because
the conformance gate certifies values, not realization — eager and lazy
heads produce identical values — and unit.edn even baked PersistentList in
as expected. test.check's for-all-takes-multiple-expressions (which counts
side effects in a for-all body) exposed it.
Wrap each native producer's result in a lazy-seq node so the body, incl.
the first element, defers until forced — the forced cseq still has eager
heads, so reduce/count/dorun/etc. force on walk and there's no per-element
cost. dedupe's (seq coll) is moved inside its lazy-seq. A jolt LazySeq is
now recognized by coll?/empty, the analyzer's form predicates (a macro can
build its expansion with map), value-host-tags + instance? (LazySeq/ISeq/
Sequential), and reports clojure.lang.LazySeq.
Kept the native Scheme implementations rather than porting Clojure's: a
straight lazy-seq+cons port is 3x slower and Clojure's chunked fast path is
288x slower because jolt's chunk machinery is unoptimized (filed jolt-j9dz);
the wrapped natives are Clojure-lazy at native speed.
+12 corpus rows (laziness at construction, LazySeq type, both JVM-certified).
make test + shakesmoke green, selfhost holds, 0 new divergences.
Close clojure.spec.alpha's remaining gaps — its conform/explain/describe/multi-spec
suite (clojure.test-clojure.spec, multi-spec) now passes fully.
- (get reify k) / (:k reify) routes to a reify's clojure.lang.ILookup valAt. spec
reifies fspec/regex specs as ILookup and reads (:args spec) off them, so before
this instrument never saw the args spec.
- A failed numeric comparison reports the JVM class: a nil operand is
NullPointerException, a non-number is ClassCastException (was an opaque :object
condition). conform-explain checks the thrown class.
- A quoted / macro-form #inst / #uuid literal constructs its Date/UUID value, like
the JVM reader (which builds it at read time). emit-quoted was emitting the raw
tagged form, so #inst "1939" and #inst "1939-01-01T00:00:00.000-00:00" weren't =.
- An anonymous fn reports class clojure.lang.AFunction$fn (the $fn marker), so
spec's fn-sym returns ::s/unknown for it, matching the JVM's ns$fn__N.
- A fn with & {:as m} kwargs accepts a trailing map (Clojure 1.11): (f :a 1 {:b 2})
and (f {:a 1}) both bind m, by merging an odd trailing map over the pairs.
- A thread responds to .getStackTrace (empty — jolt does TCO).
clojure.test-clojure.instr does not fully pass: its ::caller assertions need the
calling fn's stack frame, which TCO erases (an inherent host divergence, like the
JVM keeping tail frames).
make test green (+4 corpus rows, 0 new divergences), shakesmoke byte-identical.
Re-mint (backend emit-quoted + the destructure macro).
jolt's reader had no case for #: , so #:event{:type :search} died as an unknown
tagged literal. Now #:ns{...} qualifies each bare keyword/symbol key with ns
(:_/x stays unqualified, an already-qualified key is left alone); #::{...} uses
the current ns and #::alias{...} resolves the alias — matching Clojure.
clojure.spec.alpha's multi-spec test (which builds #:event{...} event maps) now
passes.
make test green (+1 corpus row, 0 new divergences), shakesmoke byte-identical.
One re-mint (the reader is a seed source).
jolt fns reported (class f) = clojure.lang.IFn, so they carried no defining
symbol — clojure.spec.alpha's fn-sym (which reads a fn's class name to recover its
symbol) produced garbage, so explain-data's :pred for a bare-fn predicate was `/`
instead of e.g. clojure.core/keyword?.
Now def-var! records proc -> (ns . name) (first def of a proc wins, so an alias
like (def inc' inc) doesn't rename inc), and jolt-class-name returns "ns$munged"
for a known fn — matching the JVM, where (class odd?) is clojure.core$odd_QMARK_.
A munged fn class's ancestors include clojure.lang.AFunction's hierarchy
(IFn/AFn/Fn/Runnable/Callable), so (ancestors (class f)) still holds. Anonymous /
unregistered fns stay clojure.lang.IFn (fn-sym yields :unknown, as on the JVM).
This fixes explain-data / s/form / s/describe of bare-fn predicates in
clojure.spec.alpha (and unblocks parts of its suite + test.check's reporter test).
make test green (+1 corpus row, the (type inc) unit row updated to the JVM value),
shakesmoke byte-identical, runtime only (no re-mint).
General fixes from clojure.spec.alpha's test suite.
- (symbol a-var) returns the var's qualified symbol (clojure.spec.alpha/->sym).
- clojure.lang.Compiler/demunge reverses Clojure's name munging
("clojure.core$odd_QMARK_" -> clojure.core/odd?); spec's fn-sym uses it.
- clojure.lang.MultiFn .dispatchFn / .getMethod — spec's multi-spec walks a
multimethod through them.
- (.applyTo f args) applies a fn to a seq of args (spec instrument).
Most of spec.alpha's conform/explain/describe suite passes. Remaining gaps:
explain-data's :pred for a BARE fn predicate (jolt fns don't carry their defining
symbol, so fn-sym can't recover it), #inst form rendering, and instrument — follow-up.
make test green (+3 corpus rows, 0 new divergences), runtime only (no re-mint).
- (proxy [ThreadLocal] [] (initialValue [] body)) now builds a real per-thread
store backed by a Chez thread-parameter, with a lazy initialValue; .get/.set/
.remove work. Other proxies stay nil. test.check's no-seed PRNG (next-rng) uses
one, so gen/sample and gen/generate (and everything built on them) now work.
- clojure.test/*testing-vars* (+ *report-counters*) are bound vars now, so a
defspec run through its :test metadata / default reporter doesn't hit an unbound
var.
make test green (+1 corpus row), shakesmoke byte-identical. One re-mint (proxy).
More general fixes from clojure.test.check's own suite.
- *unchecked-math* on doubles: unchecked-* only wrap integer math; on a flonum
operand they're an ordinary float op (Clojure: (unchecked-multiply 1.5 2.0) =>
3.0). test.check's rand-double is (* double-unit shifted) under *unchecked-math*
and was truncating to a long 0, so every distribution-driven generator (choose,
vector, …) collapsed to its lower bound.
- (take Double/POSITIVE_INFINITY coll) takes the whole coll instead of throwing
on the infinite count coercion (rose-tree unchunk relies on it).
- (java.util.UUID. msb lsb) 2-long constructor (the uuid generator), formatted as
the canonical lowercase 8-4-4-4-12 string; (Long. n) constructor; BigInteger
.shiftLeft / .shiftRight (size-bounded-bigint); number methods now receive args.
- A transient (ITransientSet) responds to .contains / .valAt / .count
(distinct-collection generators).
make test green (+3 corpus rows, 0 new divergences), runtime only (no re-mint).
Two general fixes shaken out by clojure.test.check's own suite (its splittable
PRNG mixes 64-bit longs and binds locals named min/max).
- *unchecked-math* now wraps arithmetic a macro emits. The analyzer rewrote a
bare (+/-/*) to its wrapping unchecked-* under *unchecked-math*, but a macro's
syntax-quote produces clojure.core/* (qualified), which was skipped — so e.g.
test.check's mix-64 multiply grew to a bignum instead of a 64-bit long. The
rewrite now also fires on the clojure.core-qualified form.
- A local binding named like a bare-emitted native op no longer shadows it. ops
where native-ops maps the name to itself (+ - * / < > min max …) emit as the
bare Scheme name; a local `max` emitted the same token, so
(fn [max] (clojure.core/max …)) called the param. munge-name now prefixes such
locals, like reserved words (derived from native-ops so they can't drift).
make test green (+1 corpus row, 0 new divergences), shakesmoke byte-identical.
One re-mint (analyzer + backend).
algo.monads' writer monad extends a protocol to clojure.lang.IPersistentList,
but jolt's lists only reported ASeq/ISeq in value-host-tags, so writer-m-add
didn't dispatch ("No method writer-m-add"). jolt models every seq as a list (no
distinct LazySeq — (class (map inc xs)) is PersistentList), so a seq now also
reports PersistentList / IPersistentList / IPersistentStack, in value-host-tags
and host-type-set. extend-protocol clojure.lang.IPersistentList then dispatches
on a list.
algo.monads passes its whole suite (11/11) over tools.macro. Listed in docs +
site. Runtime only, no re-mint. make test green (+1 corpus row, 0 new
divergences), shakesmoke byte-identical.
jolt modelled letfn as a special form directly, so (macroexpand-1 '(letfn …))
returned the form unchanged. Clojure's letfn is a macro that expands to letfn*,
and macroexpansion tooling (tools.macro, tools.analyzer) depends on that — its
special-form handlers key on letfn*, not letfn.
Split it the Clojure way:
- letfn* is now the special form (analyzer), taking flat name/fn-form pairs
[name1 fn1 name2 fn2 …] — the letrec :let lowering is unchanged.
- letfn is a macro (00-syntax) turning each (name [params] body*) spec into a
name + (fn name [params] body*) binding, so it expands to letfn*.
So (macroexpand-1 '(letfn [(f [x] x)] (f 1))) now yields
(letfn* [f (fn f [x] x)] (f 1)), and clojure.tools.macro passes its whole suite
(macrolet / symbol-macrolet / mexpand-all). Listed in docs + site.
make test green (+1 corpus row, 0 new divergences), shakesmoke byte-identical.
One re-mint (analyzer + the letfn macro); selfhost holds.
Two general fixes shaken out by clojure/tools.macro.
- The ns macro now accepts a vector reference clause [:require …] / [:use …],
not just the list form (:require …). Clojure dispatches on (first clause) and
accepts both; jolt silently dropped vector clauses, so a ns written with them
loaded with nothing required/used (tools.macro's test ns uses [:use …]).
- clojure.lang.Compiler/specials is now a static whose keys are the special-form
symbols (matching Clojure 1.2/1.3). Macroexpansion tooling reads
(keys Compiler/specials) to know which heads not to expand.
tools.macro itself isn't fully passing yet — its mexpand-all works, but the
macrolet/symbol-macrolet tests need letfn to macroexpand to letfn* (jolt models
letfn as a special form, not a macro over letfn*), so it stays off the list.
make test green (+1 corpus row, 0 new divergences), shakesmoke byte-identical.
One re-mint (the ns macro).
data.priority-map's whole suite passes (4/4). It leans on deftype/collection
interop jolt got wrong; four general fixes:
- rseq dispatches to a deftype's clojure.lang.Reversible.rseq method instead of
always demanding a vector/sorted-coll (natives-seq.ss).
- a deftype method declared at two arities from two interfaces now dispatches by
arity: the priority-map has seq[this] (Seqable) and seq[this ascending]
(Sorted), so (.seq pm false) must reach the 2-arg one. find-method-any-protocol
now matches the call's arg count via procedure-arity-mask, and a deftype's own
declared method wins over the generic collection interop in dot-forms.
- (empty x) on a deftype/record with its own empty method uses it rather than
returning {} (jolt.host/jrec-method? gate in clojure.core/empty).
- clojure.lang.Sorted (comparator / entryKey / seqFrom) works on jolt's
sorted-map/set, so subseq/rsubseq run — including the priority-map delegating
.comparator to its backing sorted-map (dot-forms.ss + host-static.ss).
Listed in docs/libraries.md + the site. One re-mint (clojure.core/empty);
everything else runtime. make test green (0 new divergences), shakesmoke
byte-identical.
Completes the JVM long-compatibility gap so clojure.test.check (and the
property-based suites built on it, e.g. data.codec) run on jolt.
A ^long is 64-bit but a Chez fixnum is only 61-bit, so the backend's fast fx
comparison / quot / min / max / inc / dec ops raised on a full-width long (one
from the PRNG or wrapping arithmetic). They now go through the jolt-l* macros
(host/chez/seq.ss): the fx fast path when the operands ARE fixnums, the generic
op otherwise — so e.g. ((fn [^long a ^long b] (< a b)) Long/MAX 1) is false, not
an error. Arithmetic +/-/* keep the raw fx ops (under *unchecked-math* they're
already the wrapping unchecked-*).
Also fixes unsigned-bit-shift-right: it was an arithmetic (sign-propagating)
shift, now a logical shift over the 64-bit two's-complement window, so
(unsigned-bit-shift-right -1 1) is 2^63-1 like the JVM.
Result: test.check 1.1.3 loads and runs (generators, quick-check, shrinking);
data.codec's base64 property suite passes (12/12 defspecs; the 2 deftests check
clojure.lang.IFn$OLLOL, a JVM primitive-fn interface, N/A). Both added to
docs/libraries.md + the site.
re-mint (backend/seed). make test green (+3 corpus rows, 0 new divergences,
numeric gate updated to the jolt-l* ops), shakesmoke byte-identical.
clojure.core's unchecked-* (and +/-/*/inc/dec under *unchecked-math*) are long
ops that WRAP on overflow; jolt's checked arithmetic is arbitrary-precision and
its unchecked-* were plain non-wrapping (+ x y), diverging from the JVM. Now they
truncate to the low 64 bits as a signed long, matching Clojure:
(unchecked-add 9223372036854775807 1) => -9223372036854775808
(unchecked-multiply 9223372036854775807 …) => 1
- host/chez/seq.ss: jolt-wrap64 + binary jolt-unc{add,sub,mul,inc,dec,neg}2 and
the variadic clojure.core/unchecked-* fns (def-var!'d in natives-seq.ss, where
def-var! is bound). The overlay's plain unchecked-* defns are removed.
- backend lng-ops: unchecked-+/-/* emit the wrapping jolt-unc* helpers (the
raising fx ops can't wrap on Chez's 61-bit fixnums); unchecked-inc/dec too.
- *unchecked-math* is honored: the analyzer reads it (jolt.host/unchecked-math?)
and rewrites +/-/*/inc/dec to their unchecked-* for the rest of a file that
(set!)s it, like the JVM.
- jolt->fx: a ^long value that overflows the 61-bit fixnum range passes through
as an exact integer instead of erroring (a full-width long from wrapping math).
Also adds Long/bitCount / numberOfLeadingZeros / reverse and Math/getExponent /
scalb (test.check's splittable PRNG uses them).
This lets clojure.test.check load and run quick-check on jolt. re-mint (analyzer/
backend/overlay are seed sources). make test green (+6 corpus rows, 0 new
divergences, numeric gate updated), shakesmoke byte-identical.
clojure.data.csv runs its whole suite on jolt (4/4 reading/writing/eof/line-
endings). Three general gaps fixed, all runtime, no re-mint, JVM-certified:
- The prefix-list form of :require/:use — (:require (clojure [string :as str]))
means clojure.string :as str — now expands (loader.ss). It silently failed
before, trying to load a "clojure" namespace.
- extend-protocol to java.io.Reader / Writer / StringReader / PushbackReader now
dispatches: those reader/writer host tags carry the right class names in
value-host-tags AND are in host-type-set, so extend-protocol registers under
the canonical tag instead of a local ns tag (records.ss). data.csv's
Read-CSV-From protocol extends to String / Reader / PushbackReader.
- (str StringWriter) returns its accumulated content (register-str-render for the
"writer" jhost), not the opaque host object — data.csv writes CSV to one and
reads it back.
Listed in docs/libraries.md + the site.
make test green (+2 corpus rows, 0 new divergences), shakesmoke byte-identical.
clojure.zip was missing xml-zip — a zipper over xml {:tag :content} elements,
which clojure.data.zip and any xml-zipper code needs. Added (runtime, loaded on
require). clojure.data.zip's whole xml suite (9/9) then passes, once XML parsing
is provided: clojure.xml/parse now ships in jolt-lang/xml over its
javax.xml.stream pull parser (committed there).
Listed in docs/libraries.md + the site.
clojure.core.contracts (over core.unify) now runs its whole suite on jolt —
14/14 across contracts/constraints/with-constraints/provide tests. Two general
gaps fixed:
- Symbol and Keyword now report IFn (and Fn/Runnable/Callable) in the modeled
class hierarchy, so a (class x)-dispatched multimethod with an IFn method
matches a symbol or keyword, like the JVM (both implement IFn — they're
callable). core.contracts' funcify* dispatches on (class constraint) and a
bare predicate symbol must hit the IFn arm. Runtime, no re-mint.
- A live Var value spliced into a form by a macro (defcurry-from resolves a var
and emits (~v l r)) now compiles: analyze treats a var-cell form as a
:the-var reference by ns+name, the same node as (var ns/name), mirroring the
existing spliced-namespace (~*ns*) case. analyzer.clj + host-contract.ss,
re-mint (prelude stays byte-identical; only the analyzer image changes).
Listed in docs/libraries.md + the site.
make test green (+2 corpus rows, 0 new divergences), shakesmoke byte-identical.
Running clojure.tools.reader's own suite on jolt surfaced a batch of general
gaps (all runtime, JVM-certified, no re-mint — reader.ss is loaded at runtime
and jolt-core has no octal literals, so selfhost holds):
Reader:
- (load "rel") resolves a non-/ path against the current namespace's directory,
like Clojure — (load "common_tests") from clojure.tools.reader-test loads
clojure/tools/common_tests.clj. Was resolved against the roots directly.
- Octal integer literals: 042 reads as 34, not decimal 42; octal string escapes
(\377 is one char, not \0 + "00"). \oNNN char octal already worked.
- (symbol nil name) now equals (symbol name) and the reader literal — a nil
namespace is the #f no-ns sentinel, not jolt-nil (jolt= compares ns by equal?).
clojure.test:
- thrown-with-msg? honors the class hierarchy (instance?) before falling back to
a simple-name match, so (thrown-with-msg? RuntimeException ...) matches an
ExceptionInfo, like thrown? already did.
Host interop (java layer):
- java.util.regex: Pattern.matcher / Matcher.matches / .group / .groupCount /
.find, and Pattern/compile.
- clojure.lang: RT/map, PersistentList/create, PersistentHashSet/createWithCheck.
- java.lang.Character: digit / isDigit / isWhitespace / valueOf.
- java.util.LinkedList (Deque surface over the ArrayList backing); ArrayList /
LinkedList are now seqable.
- BigInteger 2-arg ctor (string, radix) + .negate / .bitLength / .signum / .abs;
BigInt/fromBigInteger and Numbers/reduceBigInt (identity on jolt's exact ints).
Suite: reader_test 22/30, reader-edn_test 13/16. The remaining failures are
fundamental numeric-model differences (no BigDecimal type; BigInt and Long are
one exact-integer type) or need JVM reflection (record/ctor tagged literals via
getConstructors) — out of scope.
make test green (+8 corpus rows, 0 new divergences), shakesmoke byte-identical.
Running clojure.core.typed's runtime contract tests (typed/runtime.jvm,
test_contract — 5/5 pass) surfaced two general jolt gaps, both runtime, both
JVM-certified:
- instance? Object / java.lang.Object returned false for everything. Object is
the root of the type hierarchy: every non-nil value is an instance of Object,
nil is not. core.typed's (instance-c Object) contract depends on this; many
libraries do.
- @Compiler/LINE and @Compiler/COLUMN (clojure.lang.Compiler statics — Vars on
the JVM holding the line/column of the form being compiled) were unresolved.
Macros read @Compiler/LINE as a fallback when &form carries no position. Now
backed by derefable cells updated per top-level form, like *current-source*.
The core.typed type checker itself (tools.analyzer.jvm + ASM bytecode +
clojure.lang.Compiler internals) and the cljs runtime are not portable, so the
checker/check-ns surface is out of scope; this is the runtime contract layer.
make test green (+4 corpus rows, 0 new divergences), shakesmoke byte-identical.
EPL-1.0 is superseded by EPL-2.0 (clearer jurisdiction/patent terms). Updates
the LICENSE file and README. Clojure-derived files under stdlib/ and the
vendored sci keep their original EPL-1.0 headers per the weak-copyleft terms.
Closes#206
Adds clojure.core.async's higher-level dataflow API as a Clojure overlay
(stdlib/clojure/core/async.clj) over jolt's native channel primitives, plus
clojure.core.async.lab. The native layer (host/chez/java/async.ss) gains
offer!/poll!, put specs and :priority/:default in alts!, a transducer
ex-handler arg to chan, unblocking-buffer?, promise-buffer, and on-caller?
handling for put!/take!. The overlay covers alts!/pipe/pipeline/split/
reduce/transduce/into/take/mult/mix/pub-sub/map/merge/onto-chan/to-chan and
the deprecated map</map>/filter>/... family (rewritten as go-loops since the
JVM versions reify the impl handler protocol jolt doesn't expose).
Loading: the native primitives pre-seed clojure.core.async, so the loader now
drops it from the loaded set and a require pulls the overlay from the source
roots like clojure.test (AOT-bundled into built binaries).
Running clojure/core.async's own suite shook out two general bugs:
- :refer with a list form, (:require [ns :refer (a b c)]), dropped the names
(only the vector form was handled) — chez-register-spec! now accepts both.
- (range 0) / (range 5 5) returned nil instead of the empty seq () — empty
ranges now match Clojure, so (= () (range 0)) holds.
Suite: async_test 15/20, pipeline_test 7/7, timers_test 2/2, lab_test 2/2.
The five non-passing async_test cases all assert JVM go-machine limitations
jolt's thread-based model is a superset of (the 1024 pending-op cap, parking
ops that must throw outside a go block, expanding-transducer buffer
backpressure) or dispatch-thread identity, not data semantics.
make test green (0 new divergences, +4 range corpus rows), shakesmoke
byte-identical.
A macro body can now read &form (the call form) and &env (a map of the in-scope
local symbols), like Clojure. This is what core.logic's matche/defne use to tell
a pattern symbol that names an enclosing local from a fresh pattern var — so
locals-membero and the recursive checko in `matches` now compute correctly. The
suite reaches 535/2/0 (the last two are constraint reification ORDER, where the
constraint set is right but it is spliced from a set whose iteration order differs
from the JVM — a host set-ordering divergence, not a bug).
&form/&env are clojure.core dynamic vars bound around each expander call rather
than prepended params, so the macro calling convention is unchanged and the mint
stays consistent (the seed prelude is byte-identical; only the analyzer carries
the env into form-expand-1). macroexpand-1 passes an empty env.
corpus.edn: the ~@ unquote row is now a boolean compare (a bare clojure.core/
unquote-splicing symbol evaluates to an unbound var, not the symbol).
Two general fixes that clear core.logic's finite-domain -difference, safefd, and
the defne quoted-list patterns (form->ast), taking the suite to 532/5/0.
- instance? on a deftype matched a simple type name against the qualified tag by
raw string suffix, so "a.b.MultiIntervalFD" tested true for IntervalFD. The
suffix must land on a "." boundary. core.logic's fd dispatches on
(interval? x) = (instance? IntervalFD x), and a MultiIntervalFD wrongly counted
as an interval, so -difference/safefd computed the wrong set.
- the reader reads ~ / ~@ as clojure.core/unquote(-splicing), like the JVM reader,
instead of a bare unquote. Code that inspects quoted pattern/template data —
core.logic's defne checks (= f 'clojure.core/unquote) — now sees the symbol it
expects, so '(fn ~args . ~body) patterns compile. hc-head-is? accepts the
qualified head in syntax-quote lowering; the value-preserving change leaves the
minted seed byte-identical.
corpus.edn: 2 JVM-certified unquote rows. unit.edn: two reader rows updated to the
qualified unquote. make test + shakesmoke green, 0 new divergences, self-host holds.
value-host-tags returned only ("Object") for a record, so a protocol extended to
clojure.lang.IRecord / IPersistentMap / Associative / Seqable / … (and not to the
record's own type) dispatched to the Object default. core.logic extends IWalkTerm
to IRecord, and walking a record value hit Object's (walk-term [v f] (f v)) — which
re-enters walk* and loops forever (the test-53-lossy-records hang).
A defrecord now carries the map/record interface tags it genuinely satisfies. The
record's own type is still tried first (jrec-tag, before these tags), so a direct
extend to the record type wins, and record equality / map? / record? are unchanged.
A bare deftype stays opaque (its tag + Object; declared interfaces dispatch via its
inline methods). Runtime only, no re-mint.
A name a namespace excludes from clojure.core (:refer-clojure :exclude) is not
clojure.core/name even before the ns defines its own — syntax-quote must qualify
it to the current ns, like Clojure. refer-clojure was a no-op, so a syntax-quoted
excluded name (core.logic.fd's `==`, referenced by a constraint's -rator before fd
defines ==) resolved to clojure.core/==.
jolt-refer-clojure now records the :exclude set per ns; hc-sq-symbol consults it
before falling back to clojure.core. Fixes core.logic's fd constraint -rator names.
Runtime only, no re-mint.
Follow-on to the core.logic relational-engine work. These clear every crash in
core.logic's constraint-logic-programming and unifier layers (33 errors -> 0) and
most of the value mismatches; the suite goes 504 -> 523 passing assertions. All
are general gaps, not core.logic-specific.
- symbols intern their ns/name strings (JVM Symbol.intern .intern()s them): two
separately-read `?a` symbols now share one name-string object. core.logic's
non-unique lvars compare names by identity (via (str sym)), so without this a
term's lvar and a constraint's lvar built from different `?a` reads never matched
and constraints silently never fired.
- (str x) of a single arg returns its rendering directly instead of copying through
string-append, and a symbol stringifies to its (interned) name — JVM (str x) is
x.toString(). Needed for the identity comparison above.
- a clojure.core-qualified special form dispatches correctly: syntax-quote
namespace-qualifies a macro like letfn to clojure.core/letfn (matching Clojure,
where it's a macro), and the analyzer now maps that back to the special form
instead of treating it as an invoke of a nil var. core.logic's fnc/defnc emit
(clojure.core/letfn ...). Re-mint.
- (disj nil ...) is nil (JVM), instead of crashing in the set path — core.logic's
constraint store does (disj (get km v) id) where the get can be nil.
corpus.edn: 4 JVM-certified rows. make test + shakesmoke green, 0 new divergences,
self-host fixpoint holds.
Running clojure/core.logic's own suite surfaced a batch of general jolt gaps.
None are core.logic-specific; each is a language/host behavior that was wrong or
missing. With these, the core relational engine (unify, run/fresh/conde,
conso/membero/appendo, reification to _0/_1, lcons) runs; the remaining failures
are in core.logic's constraint-logic-programming and finite-domain layers
(tracked separately).
- analyzer: accept the list-member dot form (. target (method args)), sugar for
(. target method args). Re-mint.
- identical? is reference identity (eq?), not value equality. It was aliased to =,
which infinite-loops when a deftype's .equals short-circuits on (identical? this o)
(core.logic's Substitutions) and is wrong for distinct equal collections.
- jrecs use a deftype's declared hashCode/equals/equiv for map/set keying instead
of structural field comparison, so metadata-wrapped keys still match (core.logic
keys substitutions on lvar id, ignoring metadata).
- meta/with-meta dispatch to a deftype's clojure.lang.IObj meta/withMeta methods
when present, so metadata threaded through the type's own assoc/withMeta survives
(previously kept in an identity side-table the reconstructed instances didn't share).
- coll?/seqable? on a deftype require IPersistentCollection (cons) or ISeq (first);
ILookup(valAt)/Indexed(nth)/Counted(count)/Seqable(seq) alone no longer qualify,
matching the JVM.
- syntax-quote resolves a bare symbol to the compile ns's own def before
clojure.core, so a name the ns excluded and redefined (core.logic's == after
:refer-clojure :exclude) qualifies correctly in macro output.
- reader: record literals #ns.Type{...} / #ns.Type[...] expand to the map->/->
factory call.
- structmap API: defstruct/create-struct/struct-map/struct/accessor (map-backed,
insertion-ordered). Re-mint.
- .hashCode on strings/symbols (Java String.hashCode, Symbol Util.hashCombine);
Class.isInstance; java.util.Collection.contains over vector/list/set;
clojure.lang.RT/nextID and clojure.lang.Util hash/hasheq/equiv/identical statics.
corpus.edn: 8 JVM-certified rows. unit.edn: a Counted+Seqable deftype is coll?=false
(was a stale expectation encoding the old behavior).
jolt maps were HAMTs with hash iteration order; Clojure keeps small maps as
PersistentArrayMap (insertion order), converting to PersistentHashMap past a
threshold. Map literals, array-map, assoc, into/transient, merge, zipmap,
select-keys, update-keys/vals, frequencies and group-by now iterate in insertion
order for <=8 entries, matching the JVM. hash-map and >8-entry maps stay hash
order; sets stay hash order.
The pmap record gains an order field (the insertion-order key list, or #f once
hashed); the HAMT still backs the values so equality/hash/lookup are unchanged.
pmap-fold visits an array-mode map last-to-first so the runtime's cons-accumulate
idiom reconstructs insertion order without touching its many call sites, and
hash-mode output stays byte-identical; pmap-fold-fwd visits in order for the few
sites that build a value directly. Transient maps track insertion order and
promote to hash past max(8, source-count), matching TransientArrayMap.
The hash-map native-op retargets to a hash-order builder so (hash-map ...) stays
hash-ordered while {...} literals are ordered; syntax-quote builds maps via the
hash builder (Clojure expands `{...} to apply hash-map). The core overlay map
builders seed from {} instead of (hash-map) to keep order.
Threshold is 8 for any key (the keyword exception in newer Clojure isn't in
1.12.5). honeysql now passes 832/0/0; 19 JVM-certified corpus rows added.
defn- now adds :private to the var metadata (like Clojure), and ns-publics
filters those out while ns-interns/ns-map keep them — they were all the same
unfiltered scan before. A lib that introspects ns-publics (honeysql asserts
every public helper has a docstring, and that the clause set matches the public
helpers) saw the private defn- helpers and failed; now honeysql 636/8 -> 638/6
(the rest are map key-order).
jolt's case ops are codepoint-based and locale-independent, so the default
locale is a no-op token: getDefault/setDefault/forLanguageTag + ROOT/US/ENGLISH.
honeysql sets and restores the locale around formatting to assert output is
locale-stable (its Turkish-İ regression guard) — that test errored on the
missing Locale/setDefault static, now passes (honeysql 635/8/1 -> 636/8/0).
clojure.edn/read built the built-in #inst/#uuid eagerly (via read-string), so a
:readers override couldn't win and #inst applied to a non-string form (aero's
#inst ^:ref […]) threw. Read the raw form instead and let edn->value route every
tag through :readers then :default then the built-in — matching clojure.edn,
where a reader from opts wins. edn->value now also converts the (recursively
converted) metadata, since the raw path skips the read-string data seam. aero
suite: 59/0/0 (full pass). clojure.edn baked, re-minted.
meta-copy keyed metadata on the SAME cseq/lazyseq object (the else branch), so
(with-meta xs m) mutated the original list in place — Clojure's PersistentList
is immutable and withMeta returns a new list. (with-meta xs {:k xs}) thus built
a self-referential cycle (the list's metadata pointed at the list), which looped
*print-meta* printing forever — the root of aero's meta-preservation hang. Now
copies the cseq/lazyseq node like the other collections. aero suite completes:
58/0/1 (was hanging).