are let-bound its template vars, so a var inside quote never substituted:
(are [x] (special-symbol? 'x) if def) tested the literal symbol x twice.
Rebuild are on clojure.template/do-template (postwalk substitution), the
same architecture as upstream, with the same arg-count check.
This un-aborts every suite namespace whose are rows need substitution:
cts baseline moves 5302->5614 pass, 236->192 errors, 88->84 baselined
namespaces. The newly-reachable assertions also surface real divergences
now baselined and filed (edn reader strictness, Boolean ctor).
Five fixes shaken out by running jank-lang/clojure-test-suite:
- = short-circuits on identity like Util.equiv's k1 == k2, so (= s s) on an
infinite lazy seq answers true instead of walking forever. Numbers keep the
exactness-aware arm ((= ##NaN ##NaN) stays false like the JVM's).
- Calling a non-fn names the operator's CLASS in the ClassCastException, like
the JVM — never the value, whose printed form may be unbounded: ((range))
must throw, not hang rendering an infinite seq.
- realized? on a seq cell answers by its forced flag (the rest of a realized
lazy chain is a cseq under jolt's seq model), and the overlay's unsupported-
type error names the class, not the (possibly infinite) value.
- clojure.test/is dispatches a REGISTERED assert-expr method before its by-name
inline paths, like clojure.test where the built-ins are just pre-registered
methods — so an alias-qualified p/thrown? (the suite's portability helper)
isn't captured by the built-in thrown? path, which read its body as a class.
- clojure.test tracks tests and fixtures per namespace: deftest records its
defining ns, use-fixtures registers under the calling ns (no more cross-ns
clobbering), (run-tests 'ns ...) runs only those namespaces like clojure.test,
and each run-tests call prints/returns its own summary (global counters stay
cumulative for the n-pass/n-fail harness API).
Re-mint (20-coll.clj is seed; prelude only). +2 JVM-certified corpus rows;
the clojure-test fixture pins the alias-qualified assert-expr, per-call
summaries, and ns filtering.
A registered #tag data reader whose fn returns a FORM (borkdude/html's #html
expands to (->Html (str …))) was rewritten to a runtime call (reader-fn 'inner),
so the returned code became a runtime list value instead of being compiled —
(str #html [:div]) rendered the code, not "<div>". Clojure applies a data reader
at read time and substitutes its result as code.
loader.ss now applies the reader at load time: a code form (a list) is spliced in
to be compiled, a value (time-literals #time/date -> a Date) keeps the runtime
call, which also keeps a non-serializable constant out of an AOT build. The build
emit path never applied data readers at all (a #tag literal failed a `jolt build`
with "unsupported form"); emit-image.ss gets an ei-emit-form-hook the build sets
to the same rewrite, left as a no-op elsewhere so the seed mint (which doesn't
load loader.ss) is unaffected and the self-host byte-fixpoint holds.
Also make clojure.test report the actual values of a failing (is (= a b)) — it
printed only the form. Restricted to the common pure predicates so a macro head
still takes the plain path.
Fixture test/chez/datareader-app + a smoke check (interpreted) and a build-smoke
check (AOT). make test green, no corpus change.
jolt's `is` was a fixed macro with no assert-expr multimethod, and the runner
bypassed the report multimethod, so libraries couldn't register custom
assertions or custom report types (e.g. test.check's ::trial/::shrunk).
Add assert-expr (2-arg [msg form], dispatch on the form's first symbol /
:default / :always-fail), do-report routing through report, and report
:pass/:fail/:error methods that feed the counters. `is` dispatches to an
explicitly-registered assert-expr method before its inline path, so thrown?/
thrown-with-msg?/= and every built-in form stay byte-identical.
Runtime stdlib only, no re-mint. test/chez/clojure-test.clj self-checks the
extension points + full is/are/testing/thrown?/use-fixtures surface; smoke gate
runs it.
- (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).
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.
Six correctness fixes, each a general gap (not hiccup-specific):
- deftype is not a map. jolt treated every deftype instance as a map
(map?/record?/seqable over its fields); in Clojure only a defrecord is
map-like, a bare deftype is an opaque object. defrecord now marks its type;
map?/record?/coll?/seq/empty? gate on it, while a deftype implementing a
collection interface still dispatches through its methods.
- cross-ns extend-protocol on an imported deftype. register-method built the
type tag from the *calling* ns + bare name, so (extend-protocol P Raw …) in
one ns missed a Raw value defined in another. A simple-name index resolves
the bare name to the type's real tag (local ns still wins).
- str vs print. str of a collection is its readable form (nested strings
quoted: (str ["x"]) => ["x"]); print leaves them raw. jolt defined print
as str, conflating the two. Split via a __print1 seam.
- clojure.test thrown? now honors the exception hierarchy (instance?), so
(thrown? IllegalArgumentException …) matches an ArityException subclass.
- java.net.URI is value-equal (= and hash by string form).
- clojure.walk/macroexpand-all was missing; an unresolved qualified var made
the analyzer report "Unknown class walk".
deftype/defrecord + print are seed sources, re-minted. hiccup 365->381 of its
own suite; the rest are charset-encoding / var-meta niches.
class / .getClass now return a Class value that renders like the JVM —
(str c)/.toString -> "class <name>", pr -> "<name>", .getName/.getSimpleName/
.getCanonicalName work — but stays = and hash equal to its name string, so
(= (class x) String), class-keyed maps, multimethod dispatch on class, and
instance? keep working against the bare class-name tokens. instance? unwraps
a Class passed as the type arg.
clojure.test/class-match? no longer assumes (class e) is a string (a jolt-ism;
on the JVM a Class isn't a string either) — reads the name via .getName.
Matches JVM Class.toString, which libraries surface in error messages
(clojure.data.json DJSON-54 expects "...of class java.net.URI"). data.json
suite 139/139 bar the one UTF-16 surrogate test (Unicode-scalar char model).
5 corpus rows certified vs JVM; make test + shakesmoke green, 0 new divergences.
clojure.test reported a host-condition crash with a blank message (ex-message is
nil for non-ex-info conditions); fall back to jolt.host/condition-message.
condition-message itself left a Chez format-template message (open-input-file's
'failed for ~a: ~(~a~)') unformatted; apply its irritants.
Rename src/jolt -> stdlib (the runtime-loaded layer; jolt-core stays the
seed-baked layer) and update the loader / emit-image / doc paths. Drop dead
code: the spike/ experiments, the duplicate clojuredocs-export.edn (json moves
to tools/), the Janet-era jolt.http binding, and the orphaned
persistent_vector.clj whose ns/path didn't even match.
Strip porting residue from comments and docstrings across host/chez, jolt-core,
stdlib, tests, and docs: internal issue ids, "Phase N" markers, and the "vs
Janet" historical exposition, leaving present-tense descriptions and the real
JVM-Clojure semantic contrasts. Same pass over the corpus suite labels. The seed
is unchanged (docstrings/comments aren't emitted), so the self-host fixpoint and
corpus are untouched.
Port tools/spec_coverage.py off the dead janet probe to bin/joltc and regenerate
coverage.md; drop the dead :host/janet rule from certify.clj and regenerate the
conformance profile. Add docs/host-interop.md (the JVM shims and how to register
your own host class from a library) and a writing-style note in CLAUDE.md.
Stabilize the four racy concurrency corpus cases (future-cancel and agent
send/send-off): give the future a sleeping body and the agent a slow action, so
cancel reliably catches an in-flight future and deref reliably reads the
pre-update snapshot. They certify deterministically now, so drop their :flaky
allowlist entries and the orphaned legend.