jolt no longer satisfies :clj in reader conditionals. The shortcut was a measured net liability: :clj branches carry JVM interop and JVM-specific test expectations jolt fails, and they shadowed :default branches jolt passes. A/B over the suite: clj,default = 4967 assertions / 4324 pass / 119 errors; jolt,default = 5069 / 4470 / 81 (+146 pass, -38 errors, +8 clean files). Baselines raised to 4470/86. Matching is now by CLAUSE order like Clojure — the first clause whose key is in the feature set wins (#?(:default 5 :clj 6) is 5 everywhere); the old code scanned for :clj first, then :default, regardless of position. Foreign clj-targeted libraries are a property of the LOADING CONTEXT, not the platform: reader-features-set! opts a load into a compatibility set, and the SCI bootstrap/runtime tests load SCI under ["jolt" "clj" "default"] (its .cljc selects implementations via :clj with no :jolt branches). JOLT_FEATURES remains the process-wide override. RFC 0002 records the decision with the measured data; spec 02-reader S18 is now normative (clause order, documented feature set, per-context override). Reader tests updated to the portable set + an opt-in round-trip.
3.8 KiB
RFC 0002 — Reader-Conditional Feature Set
- Status: Accepted (implemented; measured)
- Created: 2026-06-10
- Spec:
docs/spec/02-reader.md§2.3 S18
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, jolt-dw4).
.cljcauthors targeting jolt can write:joltbranches and rely on:defaultfallbacks.