Shaking out clojure.core.memoize (207 assertions, 0 fail) cleared several
general gaps:
- deref/@ on a deftype or reify implementing clojure.lang.IDeref dispatches to
its deref method (RetryingDelay / make-derefable).
- deftype mutable fields (^:unsynchronized-mutable / ^:volatile-mutable) are
read live: a set! within a method is observed by a later read in the same
invocation, not the entry-time capture. Needed for double-checked locking.
Immutable fields stay let-bound. Field reads rewrite to (.-field inst) with
lexical-shadow tracking.
- def metadata values are evaluated, like Clojure: ^{:k (f)} stores (f)'s
result and ^{:af some-fn} the fn. :tag stays a literal hint.
- try dispatches catch clauses by class in order via the exception supertype
hierarchy; a non-matching value re-throws, an untyped host condition is caught
by a RuntimeException/Exception/Throwable clause. Previously the last clause
won and the class was ignored.
- locking takes a real per-object monitor (recursive Chez mutex) now that
futures/agents/threads share one heap; it was a no-op.
- supers/ancestors reflect a small modeled JVM interface hierarchy, so
(ancestors (class f)) yields Runnable/Callable (core.memoize's arg check).
- AssertionError / Error constructors.
JOLT_FEATURES is gone from the docs: it isn't read anywhere on Chez, and the
reader already includes :clj in its default feature set. RFC 0002's
{:jolt :default} design was reverted in the reader; docs now match the code.
Raises the SCI floor 205 -> 210.
4.6 KiB
RFC 0002 — Reader-Conditional Feature Set
- Status: Superseded (2026-06-25) — jolt now includes
:cljin the default set; see the note below. - Created: 2026-06-10
- Spec:
docs/spec/02-reader.md§2.3 S18
Update (2026-06-25). The default set is now
#{:jolt :clj :default}—:cljis satisfied by default. The clj ecosystem's.cljclibraries gate their host code behind#?(:clj …)with no:jolt/:defaultfallback, so the conformance libraries (core.cache, core.match, tick, malli, …) only load with:cljpresent; requiring an opt-in for each was friction with no payoff once jolt'sclojure.lang.*/java.*emulation was broad enough to run those:cljbranches. Matching is still by clause order, so a library can place a:joltbranch first to override. There is noJOLT_FEATURESenvironment variable; a loading context overrides the set at runtime withreader-features-set!. The rest of this RFC is the original (reverted) design.
Summary
jolt's reader-conditional feature set is #{:jolt :default}, matched in
clause order (the first clause whose key the platform satisfies wins).
A loading context may opt a foreign, clj-targeted library into :clj
compatibility via reader-features-set! (or process-wide via the
JOLT_FEATURES environment variable). jolt does not satisfy :clj by
default.
Background
#?(:clj … :cljs … :default …) selects a branch by platform feature at read
time. Until now jolt satisfied :clj — a compatibility shortcut inheriting
the JVM branches of .cljc files, on the theory that the :clj branch is
usually the "main" implementation. Each dialect chooses its own policy:
ClojureScript satisfies only :cljs; jank uses :jank; babashka includes
:clj because it genuinely is JVM-Clojure-compatible to a deep degree.
Two defects forced the decision:
- jolt is not JVM-compatible where it matters for
:cljbranches: they contain interop (java.util.*,deftypeover JVM classes) and encode JVM-specific expectations in tests (e.g.parse-uuid's reference permissiveness), both of which jolt fails. - The old implementation also matched by key priority (
:cljfirst, then:default) rather than clause order —#?(:default 5 :clj 6)read as6, diverging from Clojure on all platforms.
Decision and evidence
Measured A/B over the cross-dialect clojure-test-suite (identical tree, 2026-06-10):
| Feature set | Assertions reached | Pass | Fail | Error | Clean files |
|---|---|---|---|---|---|
clj, default (old) |
4967 | 4324 | 524 | 119 | 78 |
jolt, default (new) |
5069 | 4470 | 518 | 81 | 86 |
The portable convention reads more of the suite (:default branches were
being shadowed by :clj ones jolt can't satisfy) and improves every metric:
+146 passes, −38 errors, +8 clean files. The :clj shortcut was a net
liability, not a compatibility win.
The opposing case — loading real-world clj-targeted libraries — is real:
SCI's .cljc sources select their implementation via #?(:clj …)/:cljs
with no :jolt branches, and fail to load under the portable set. That is a
property of the loading context, not of the platform: the resolution is
per-context opt-in, exactly how the SCI bootstrap now loads
((reader-features-set! ["jolt" "clj" "default"])).
Specification (normative, mirrored in spec §2.3 S18)
- The platform feature set is implementation-defined and MUST be
documented. jolt's is
#{:jolt :default}. - Matching MUST be by clause order: the first clause whose key is in the
feature set wins.
:defaultmatches on every platform.#?(:default 5 :clj 6)is5everywhere. - An unmatched conditional reads as nothing (no form); an unmatched
#?@(…)splices nothing. - Implementations SHOULD provide a per-loading-context override so foreign
libraries written for other dialects can be read under a compatibility
set; using it is a deliberate, scoped decision (jolt:
reader-features-set!/JOLT_FEATURES).
Consequences
- Suite baselines re-measured and raised:
baseline-pass4324 → 4470,baseline-clean-files78 → 86. - Reader tests assert the portable set + clause-order semantics, plus one
opt-in round-trip through
reader-features-set!. - Loading clj-ecosystem libraries via deps requires deciding their feature set; the deps loader currently inherits the process default — a future refinement is per-dependency feature configuration (filed with the deps work).
.cljcauthors targeting jolt can write:joltbranches and rely on:defaultfallbacks.