Skip to content

Host classes are provided by declaration, not by a list in the runtime - #813

Merged
yogthos merged 1 commit into
mainfrom
feat/declared-host-class-providers
Sep 1, 2026
Merged

yogthos merged 1 commit into
mainfrom
feat/declared-host-class-providers

Conversation

@yogthos

@yogthos yogthos commented Sep 1, 2026

Copy link
Copy Markdown
Member

Implements RFC 0014.

The runtime carried the mapping from host class names to the library that installs them — jolt.crypto, io.github.jolt-lang/jolt-crypto, jolt.socket, jolt.time, named in host-static.ss. About 140 of that file's 552 lines were that bookkeeping, in two parallel mechanisms (a bespoke one for java.time and the general lib-class-providers) invoked side by side at five call sites, plus a sixth copy in build.ss whose comment said it "mirrors the runtime predicates".

That was not untidiness. Core decided what a class meant, so providers were unswappable and adding one needed a jolt release. And the list was hand-synced — the source said so out loud, next to a recorded instance of the bug it caused:

This list has to name EVERY class jolt-lang/time registers, in both the simple and the fully-qualified spelling. […] DateTimeFormatterBuilder being absent kept malli's transform.cljc from loading while the qualified spelling worked. When the library grows a class, add it here.

Why declaration, and not register-on-load

A provider cannot register itself as it loads: the miss happens before it has loaded, which is the entire purpose of the mechanism. So the mapping arrives as data, ahead of the load:

;; jolt-lang/db
:jolt/provides {db.jdbc-shim ["java.sql.ResultSet" "java.sql.Connection" …]}

jolt.deps collects it through the walk it already does for :jolt/native; apply-project! installs it before any user code compiles. Registration at load is unchanged. Fully-qualified only — the runtime derives the simple spelling.

What core keeps

Only the classes core implements: jolt.time.base's java.time value types (RFC 0008) and jolt.socket's java.net surface, as core-class-providers seeding the same table, carrying no coordinate because no dependency supplies them. build.ss now asks lib-provider-for rather than restating its rules.

Decisions

  • Conflicts error at resolve time naming both claimants. Whichever won would decide what java.sql.Connection means program-wide, and the loser's shim would be half-installed — registered for the classes nobody else claimed, silently absent for the rest.
  • A claim on a class the runtime already implements is refused — checked against the statics/ctor tables, not the hierarchy. The hierarchy knows names it does not implement (java.time.ZoneId is in it so isa? answers correctly), and checking it rejected every provider for the classes the mechanism exists to serve.

Errors no longer name a library

No dependency provides java.time.ZoneOffset — a concrete implementation of the
JDK classes must be provided.

Which library supplies java.sql is not the runtime's to say, and a caller may write the shim themselves. Two unit tests pinned the old wording — which is how deliberate that message was; they assert the general form now, plus a third asserting no library is named, so the coupling cannot return as a string. A provider that is declared but raised while loading still names itself: that is the caller's own dependency read back from their own deps.edn, and the fix is the opposite of adding something.

Verified

make test — 93 ci targets, 3 test targets, clean. The gate caught four things targeted testing did not: the two deps-alias fixtures needed declarations; the lib coordinate is a symbol the failed-to-load message string-appends; build-app supplies providers on its own :paths (the project's-own-declaration case); and the new jolt.host seam had to be added to jolt-host-manifest.txt.

Declarations are already merged in jolt-lang/db#17, jolt-lang/time#11 and jolt-lang/crypto#6.

RFC 0014. The runtime carried the mapping from host class names to the library
that installs them — jolt.crypto, io.github.jolt-lang/jolt-crypto, jolt.socket
and jolt.time, named in host-static.ss. Roughly 140 of that file's 552 lines
were the bookkeeping, in two parallel mechanisms (a bespoke one for java.time
and the general lib-class-providers) invoked side by side at five call sites,
plus a sixth copy in build.ss whose comment said it mirrors the runtime
predicates.

That was not untidiness. Core decided what a class MEANT, so providers were not
swappable and adding one needed a jolt release; and the list was hand-synced,
which the source said out loud — "When the library grows a class, add it here" —
next to a recorded instance of the bug it caused.

A provider cannot simply register itself as it loads: the miss happens BEFORE it
has loaded, which is the whole point of the mechanism. So the mapping arrives as
data, ahead of the load. A library declares it in its own deps.edn:

    :jolt/provides {db.jdbc-shim ["java.sql.ResultSet" ...]}

jolt.deps collects it through the walk it already does for :jolt/native, and
apply-project! installs it before any user code compiles. Registration at load
is unchanged. Fully-qualified names only — the runtime derives the simple
spelling, which is what the old table got wrong by hand.

Core keeps only the classes core IMPLEMENTS: jolt.time.base's java.time value
types (RFC 0008) and jolt.socket's java.net surface, as core-class-providers
seeding the same table, carrying no coordinate because no dependency supplies
them. build.ss now asks lib-provider-for instead of restating its rules.

Two dependencies claiming one class is an error at resolve time naming both:
whichever won would decide what java.sql.Connection means program-wide, and the
loser's shim would be half-installed. A claim on a class the runtime already
implements is refused — the statics/ctor tables, not the hierarchy, which knows
names it does not implement.

Errors no longer name a library. Which library supplies java.sql is not the
runtime's to say, and a caller may write the shim themselves:

    No dependency provides java.time.ZoneOffset — a concrete implementation of
    the JDK classes must be provided.

Two unit tests pinned the old wording; they assert the general form now, plus a
third asserting no library is named, so the coupling cannot return as a string.
The deps-alias and build fixtures declare :jolt/provides like any other project —
the build one on its own :paths, which covers the project's-own-declaration case.

make test passes: 93 ci targets, 3 test targets.
@yogthos
yogthos merged commit 498006c into main Sep 1, 2026
6 checks passed
@yogthos
yogthos deleted the feat/declared-host-class-providers branch September 1, 2026 06:26
burinc pushed a commit to burinc/jolt that referenced this pull request Sep 2, 2026
Since jolt jolt-lang#813 a host class is provided only through a library's
:jolt/provides declaration, which jolt.deps reads from the library's deps.edn.
Nine manifest entries put time's src directory on the path instead, so
DateTimeFormatter and friends were unprovided and seven suites failed to load.
The same :local-deps mechanism the crypto and yaml providers already use
resolves time through its deps.edn.

Claude-Session: https://claude.ai/code/session_019tGgRzPx3vtr5siPkKjjLE
yogthos added a commit that referenced this pull request Sep 2, 2026
Closes the Unreleased section as 0.8.1. Four entries that PRs #806, #807,
#808 and #815 filed under 0.8.0 after that tag was cut (socket errno,
typed atomics, File.getParent at the root, namespaced-map prefixes) move
into 0.8.1, where they shipped. Adds the entry #813 never wrote: host
classes are provided by :jolt/provides declarations now, with the
migration note for time and crypto pins (v0.0.8 / v0.0.5 declare theirs).

Claude-Session: https://claude.ai/code/session_019tGgRzPx3vtr5siPkKjjLE
yogthos added a commit that referenced this pull request Sep 3, 2026
Since jolt #813 a host class is provided only through a library's
:jolt/provides declaration, which jolt.deps reads from the library's deps.edn.
Nine manifest entries put time's src directory on the path instead, so
DateTimeFormatter and friends were unprovided and seven suites failed to load.
The same :local-deps mechanism the crypto and yaml providers already use
resolves time through its deps.edn.
yogthos added a commit that referenced this pull request Sep 3, 2026
Closes the Unreleased section as 0.8.1. Four entries that PRs #806, #807,
#808 and #815 filed under 0.8.0 after that tag was cut (socket errno,
typed atomics, File.getParent at the root, namespaced-map prefixes) move
into 0.8.1, where they shipped. Adds the entry #813 never wrote: host
classes are provided by :jolt/provides declarations now, with the
migration note for time and crypto pins (v0.0.8 / v0.0.5 declare theirs).
burinc pushed a commit to burinc/jolt that referenced this pull request Sep 4, 2026
jolt-lang#813)

RFC 0014. The runtime carried the mapping from host class names to the library
that installs them — jolt.crypto, io.github.jolt-lang/jolt-crypto, jolt.socket
and jolt.time, named in host-static.ss. Roughly 140 of that file's 552 lines
were the bookkeeping, in two parallel mechanisms (a bespoke one for java.time
and the general lib-class-providers) invoked side by side at five call sites,
plus a sixth copy in build.ss whose comment said it mirrors the runtime
predicates.

That was not untidiness. Core decided what a class MEANT, so providers were not
swappable and adding one needed a jolt release; and the list was hand-synced,
which the source said out loud — "When the library grows a class, add it here" —
next to a recorded instance of the bug it caused.

A provider cannot simply register itself as it loads: the miss happens BEFORE it
has loaded, which is the whole point of the mechanism. So the mapping arrives as
data, ahead of the load. A library declares it in its own deps.edn:

    :jolt/provides {db.jdbc-shim ["java.sql.ResultSet" ...]}

jolt.deps collects it through the walk it already does for :jolt/native, and
apply-project! installs it before any user code compiles. Registration at load
is unchanged. Fully-qualified names only — the runtime derives the simple
spelling, which is what the old table got wrong by hand.

Core keeps only the classes core IMPLEMENTS: jolt.time.base's java.time value
types (RFC 0008) and jolt.socket's java.net surface, as core-class-providers
seeding the same table, carrying no coordinate because no dependency supplies
them. build.ss now asks lib-provider-for instead of restating its rules.

Two dependencies claiming one class is an error at resolve time naming both:
whichever won would decide what java.sql.Connection means program-wide, and the
loser's shim would be half-installed. A claim on a class the runtime already
implements is refused — the statics/ctor tables, not the hierarchy, which knows
names it does not implement.

Errors no longer name a library. Which library supplies java.sql is not the
runtime's to say, and a caller may write the shim themselves:

    No dependency provides java.time.ZoneOffset — a concrete implementation of
    the JDK classes must be provided.

Two unit tests pinned the old wording; they assert the general form now, plus a
third asserting no library is named, so the coupling cannot return as a string.
The deps-alias and build fixtures declare :jolt/provides like any other project —
the build one on its own :paths, which covers the project's-own-declaration case.

make test passes: 93 ci targets, 3 test targets.

Co-authored-by: Yogthos <yogthos@gmail.com>
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