Skip to content

Stop matching :bb in reader conditionals - #896

Open
yogthos wants to merge 5 commits into
fix/wp-trace-getenv-load-orderfrom
fix/drop-bb-feature
Open

Stop matching :bb in reader conditionals#896
yogthos wants to merge 5 commits into
fix/wp-trace-getenv-load-orderfrom
fix/drop-bb-feature

Conversation

@yogthos

@yogthos yogthos commented Sep 8, 2026

Copy link
Copy Markdown
Member

Fixes #893.

Stacked on #894 (base fix/wp-trace-getenv-load-order) — it re-records the same
two libconformance rows this touches, and one of them moves again here.

The reader feature set is {:jolt :clj :default} again. 0.7.10 added :bb on
the theory that a :bb branch solves the same non-JVM problems jolt has. It
does not. A :bb branch is written for babashka's host model, and the branch a
library writes for a host with no java.nio and no reflection is not the branch
for a host that has both.

Measured, matching :bb cost:

with :bb without
claxon (src) FileNotFoundException: cheshire/core — will not load loads
lasertag 58/49/9/0 58/51/7/0
tick 67/722/1/0 67/723/0/0
markdown-clj 94/180/0/0 94/182/0/0
cts (13 ns with :bb branches) 498 pass / 7 fail / 1 err 587 pass / 10 fail / 0 err
honeysql 177/2842/0/0 identical
  • claxon and aws-api both write #?(:bb [cheshire.core] :clj [clojure.data.json]).
    jolt has no cheshire, so matching :bb turned a working library into a load
    failure.
  • lasertag's two were :bb branches asserting sci.impl.fns class names and
    babashka's class-as-symbol hierarchies; tick's was a :bb branch asserting
    babashka's English-only locale rendering, where jolt renders the French
    through jolt-lang/time.
  • On cts, descendants goes green — its 3 failures were the :bb branch.
    eq, not-eq and num pick up rows: three are the float/integer box model,
    and two are ##NaN divergences :bb had been hiding, now in
    known-divergences with :checks.
  • honeysql orders #?(:bb … :clj (.sym ^Keyword k)) with :bb first at all
    three .sym sites, so jolt never reached its own keyword-direct-emit path.
    Same tally, and analyzer.clj/backend_scheme.clj no longer claim otherwise.

jolt.bb.fs and the loader's namespace-supplement seam existed only to fill the
babashka.fs/list-dir a :bb branch skips. babashka.fs defines it off its own
:clj branch now — the DirectoryStream import, Files/newDirectoryStream,
the reify DirectoryStream$Filter and (with-open [s …] (vec s)) all work
here, and fs-test passes unchanged. vendor/process goes back to upstream
babashka/process: the fork's only patch was a :jolt arm working around the
empty :bb splice, and process-test's timed-deref row pins the behaviour.

seqable? (first commit, independent)

seqable? was built out of coll?, so it answered false for a bare deftype
or reify declaring Seqable or Iterable — and for clojure.core.Eduction,
the one in core — while seq worked on all of them. malli's :every tests
seqability before it walks, so (m/validate [:every :int] (eduction …)) was
false. The predicate now reads the same per-method probes the seq arms read,
so the two answers cannot drift apart; jrec-declares-coll-iface? is
deliberately not it, since ILookup and Counted are in that list and neither
is Seqable on the JVM (corpus has the row).

:jolt/features

{:paths ["src"] :jolt/features [:bb]}

for a script ported from babashka whose :bb branches are the ones its author
wants. Additive — it can add a key, never remove one — and the project's alone,
like :jolt/replaces, because the feature set decides which branch every
library in the program is read through. Installed ahead of the class providers,
since those matter at the first class reference and this at the first form read.

malli

6757/36/34 -> 13261/28/10 — better on every counter. malli.sci's :bb branch swaps
m/eval's whole evaluator for load-string, so the suite's ~12k assertions
were running with m/eval stubbed and roughly half of them gated away
(ensure-parser-type is #?(:bb nil :default …)). Without :bb the suite
needs sci, and sci 0.12.51 from Maven does not load on jolt — its
copy-var splices the dereffed value of a macro var straight into the
emitted form, and a raw procedure is not a form jolt's analyzer takes. Filed as
jolt-l7tq; the recipe points at vendor/sci (the copy make sci and make scifunctional already gate), which clears all 72 ::sci-not-available errors,
and takes the timeout the full assertion set needs.

Three effects, no regression on any counter: half the suite was :bb-gated
(malli.parser-test alone goes 1754 -> 8224 passing assertions — its
ensure-parser-type helper is #?(:bb nil :default …), so every call was
vacuous), sci clears the 72 errors, and the seqable? fix clears the 104
identical? failures in parser-test plus 12 in core-test.

It is now the heaviest row in the gate by a wide margin: ~15GB RSS and ~30
CPU-minutes, hence :timeout 2400 and the runner's existing
JOLT_MAX_HEAP=off. Worth a look if CI machines are smaller than this one.

Gates

make test green — 104 ci targets + selfhost byte fixpoint. make libconformance on the seven affected rows: 3 ok, 4 better, 0 regressed.

Three things only the full gate caught, all in the last commit: the seed
records line numbers, so the comment edits in analyzer.clj and
backend_scheme.clj move it (make remint, converged in 2 passes); and
records-gambit.ss is generated from records-dispatch.ss among others, so
iface-seqable? had to be mirrored (make gambitgen, make gambitseed).

Site pages for cljc-interop, differences and tools-deps are in
jolt-lang/jolt-lang.github.io#29.

https://claude.ai/code/session_017f4uwd9Mq3Bn6NZLzBsjCT

Yogthos added 3 commits September 8, 2026 13:15
On the JVM seqable? is an instance? test over Seqable / ISeq / Iterable, plus
arrays, CharSequence and Map. Jolt built it out of coll?, which a bare deftype
is not, so it answered false for values seq works on perfectly well — a
(reify Iterable ...), a (deftype T [] Seqable (seq [_] ...)), and
clojure.core.Eduction, the one in core.

The predicate reads the same per-method probes the seq arms in
records-dispatch.ss read, rather than a second list of interface names, so the
two answers cannot drift apart again. jrec-declares-coll-iface? is deliberately
not it: that name list is the collection-behaviour set and ILookup, Counted and
Associative are in it, none of which is Seqable on the JVM — the corpus has a
row pinning an ILookup-only deftype as neither coll? nor seqable?.

Found through malli's :every schema, which tests seqability before it walks:
(m/validate [:every :int] (eduction (map identity) [1 2 3])) was false and
(m/parser [:every :any]) answered ::invalid on an eduction.

Claude-Session: https://claude.ai/code/session_017f4uwd9Mq3Bn6NZLzBsjCT
The feature set is {:jolt :clj :default} again. 0.7.10 added :bb on the theory
that a :bb branch solves the same non-JVM problems jolt has. It does not: a :bb
branch is written for babashka's host model, and where that model differs from
jolt's the branch is wrong here. The branch a library writes for a host with no
java.nio and no reflection is not the branch for a host that has both.

Measured against the checkouts this repo gates, matching :bb cost:

- claxon and aws-api write #?(:bb [cheshire.core] :clj [clojure.data.json]).
  jolt has no cheshire, so a working library became a load failure.
- lasertag: 2 failures from :bb branches asserting sci.impl.fns class names and
  babashka's class-as-symbol hierarchies. 49/9 -> 51/7.
- tick: 1 failure from a :bb branch asserting babashka's English-only locale
  rendering, where jolt renders the French through jolt-lang/time. 722/1 -> 723/0.
- markdown-clj: two assertions jolt passes, gated away. 180 -> 182.
- cts: 89 assertions hidden across 13 namespaces, and two ##NaN divergences with
  them. descendants goes green (its 3 failures WERE the :bb branch); eq, not-eq
  and num pick up rows that are the float/integer box model and the two NaN
  entries now in known-divergences.
- honeysql orders #?(:bb … :clj (.sym ^Keyword k)) with :bb first at all three
  sites, so jolt never reached its own keyword-direct-emit path. Tally unchanged.

jolt.bb.fs and the loader's namespace-supplement seam existed only to fill the
babashka.fs/list-dir a :bb branch skips; babashka.fs defines it off its own :clj
branch now, which is the one jolt's java.nio shims target. vendor/process goes
back to upstream babashka/process — the fork's only patch was a :jolt arm
working around the empty :bb splice, and process-test's timed-deref row pins it.

malli's row is not re-recorded here: its :bb branch swapped m/eval's evaluator
for load-string, so the suite needs sci, and sci 0.12.51 from Maven does not
load on jolt (jolt-l7tq). It points at vendor/sci instead and gets the timeout
the full assertion set needs.

A project that wants :bb read asks for it with :jolt/features.

Reported by @markokocic in #893.

Claude-Session: https://claude.ai/code/session_017f4uwd9Mq3Bn6NZLzBsjCT
…ot carry

jolt reads #?() against {:jolt :clj :default}. A project widens that for the
whole program with

  {:jolt/features [:bb]}

which is what a script ported from babashka wants now that jolt does not match
:bb itself. Additive: it can add a key, never remove one, so :clj still reads
and a :jolt clause still wins over both — a project cannot make jolt stop
reading the branches its own stdlib is written against.

The project's alone, like :jolt/replaces and :jolt/provides and for the same
reason: the feature set decides which branch EVERY library in the program is
read through, so a dependency must not change it underneath. Installed in
apply-project! ahead of the class providers, because those matter at the first
class reference and this one at the first form read.

Two smoke rows, both directions: with the key a :bb clause wins and the set has
four members; without it the same expression reads :clj and the set has three.

Claude-Session: https://claude.ai/code/session_017f4uwd9Mq3Bn6NZLzBsjCT
Yogthos added 2 commits September 8, 2026 13:46
Better on every counter, three effects and no regression:

- the suite :bb-gated roughly half its assertions and jolt was reading those
  branches. malli.parser-test alone goes 1754 -> 8224 passing: its
  ensure-parser-type helper is #?(:bb nil :default …), so every call was vacuous.
- sci is on the classpath now, so the 72 ::sci-not-available errors m/eval
  raised are gone.
- seqable? answers for a declared Seqable/Iterable, so :every over an eduction
  validates and parses — the 104 identical? failures in parser-test and 12 in
  core-test.

The row is the heaviest in the gate now: ~15GB RSS and ~30 CPU-minutes, which
is what the 2400s timeout is for. Its comment said the suite was :bb-gated as
if that were the recorded state; rewritten to say where the residue actually
sits.

Claude-Session: https://claude.ai/code/session_017f4uwd9Mq3Bn6NZLzBsjCT
The seed records line numbers, so the comment edits in analyzer.clj and
backend_scheme.clj move it even though no behaviour changed; records-gambit.ss
is generated from records-dispatch.ss among others, so iface-seqable? has to be
mirrored there. Converged in 2 passes.

Claude-Session: https://claude.ai/code/session_017f4uwd9Mq3Bn6NZLzBsjCT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant