core: *ns* — the current-namespace dynamic var

*ns* is interned in clojure.core holding the current NAMESPACE OBJECT, kept
in sync by ctx-set-current-ns through a var table cached on the env (one
table put on the hot path; core-bench A/B neutral). A thread binding
(binding [*ns* ...]) shadows the root through var-get as usual. in-ns now
returns the namespace object (Clojure); str renders a namespace as its name
and pr-str as #namespace[name]. The ns-designator helper accepts namespace
objects (they are tagged STRUCTS — the old table?-based check never matched
them), so (ns-aliases *ns*) / (ns-unalias *ns* 'a) work — SCI and the jank
syntax-quote corpus use exactly that shape.

Known divergence (documented): inside an interpreted fn, *ns* reflects the
fn's defining ns (jolt's resolution model rebinds current-ns per call);
top-level and load-time reads match Clojure.

Gate green (conformance 326x3, suite 4572 >= 4540, all batteries,
specs+unit +8 *ns* rows); bench neutral.
This commit is contained in:
Yogthos 2026-06-10 13:05:15 -04:00
parent 4f872fce94
commit 817495dd51
5 changed files with 46 additions and 9 deletions

View file

@ -3,16 +3,16 @@
Generated 2026-06-10 by `tools/spec_coverage.py` — do not edit by hand.
Surface: **694** clojure.core vars (ClojureDocs export; 648 with
community examples). jolt interns 552 of them.
community examples). jolt interns 553 of them.
| Status | Count | Meaning |
|---|---|---|
| implemented+tested | 415 | in jolt and exercised by spec/conformance |
| implemented-untested | 137 | in jolt, no direct test — spec entries will add them |
| implemented-untested | 138 | in jolt, no direct test — spec entries will add them |
| resolvable-not-interned | 2 | works in code but invisible to ns introspection (conformance finding) |
| missing-portable | 10 | portable semantics, jolt lacks it — implementation gap |
| special-form | 15 | specified in §3, not a library var |
| dynamic-var | 31 | classification needed: portable default vs host-dependent |
| dynamic-var | 30 | classification needed: portable default vs host-dependent |
| agents-taps | 22 | out of scope pending concurrency design note |
| stm-refs | 11 | out of scope pending concurrency design note |
| jvm-specific | 53 | catalogued, not specified |
@ -47,7 +47,7 @@ UNVERIFIED field; that column will be added as entries land.
| `*fn-loader*` | dynamic-var | |
| `*in*` | dynamic-var | |
| `*math-context*` | dynamic-var | |
| `*ns*` | dynamic-var | ✓ |
| `*ns*` | implemented-untested | ✓ |
| `*out*` | dynamic-var | ✓ |
| `*print-dup*` | dynamic-var | ✓ |
| `*print-length*` | dynamic-var | ✓ |

View file

@ -1612,6 +1612,12 @@
# Readable rendering of a value (Clojure pr semantics): strings quoted,
# keywords with leading ':', symbols by name, collections with their reader
# syntax. Used by both pr-str (readable) and str (collection elements).
# A namespace's :name may be a string or a symbol struct depending on the
# creation path — normalize for display.
(defn- ns-display-name [ns]
(def n (ns :name))
(if (and (struct? n) (= :symbol (get n :jolt/type))) (n :name) (string n)))
(var pr-render nil)
# Format a number the way Clojure prints it: infinity and NaN have named forms
@ -1686,6 +1692,10 @@
(and (struct? v) (= :jolt/inst (v :jolt/type)))
(do (buffer/push-string buf "#inst \"") (buffer/push-string buf (inst->rfc3339 v))
(buffer/push-string buf "\""))
(= :jolt/namespace (get v :jolt/type))
(do (buffer/push-string buf "#namespace[")
(buffer/push-string buf (ns-display-name v))
(buffer/push-string buf "]"))
(and (table? v) (= :jolt/var (get v :jolt/type))) (buffer/push-string buf (var-display v))
(core-sorted-map? v) (pr-render-pairs buf (sorted-map-entries v))
(core-sorted-set? v) (pr-render-seq buf (v :items) "#{" "}")
@ -1718,6 +1728,7 @@
(if (v :ns) (string (v :ns) "/" (v :name)) (v :name))
(and (struct? v) (= :jolt/uuid (v :jolt/type))) (v :str)
(and (struct? v) (= :jolt/inst (v :jolt/type))) (inst->rfc3339 v)
(= :jolt/namespace (get v :jolt/type)) (ns-display-name v)
(and (table? v) (= :jolt/var (get v :jolt/type))) (var-display v)
(number? v) (fmt-number v)
(= true v) "true"

View file

@ -796,9 +796,9 @@
quoted symbol arrives evaluated."
[ctx sym]
(def ns-name (if (and (struct? sym) (= :symbol (sym :jolt/type))) (sym :name) (string sym)))
(ctx-find-ns ctx ns-name)
(def the-ns-obj (ctx-find-ns ctx ns-name))
(ctx-set-current-ns ctx ns-name)
nil)
the-ns-obj)
(defn use-impl
"(use '[ns ...] ...) — refer ALL public vars of each used ns into the CURRENT ns.
@ -953,6 +953,12 @@
# Var/namespace lookups that need the ctx (the rest of the var fns — var-get/
# var-set/var?/alter-var-root/alter-meta!/reset-meta! — are plain core-bindings).
(ns-intern core "find-var" (fn [sym] (find-var ctx sym)))
# *ns*: the current-namespace dynamic var. Its root is kept in sync by
# ctx-set-current-ns via the cached var table (env :ns-var); a thread
# binding (binding [*ns* ...]) shadows the root through var-get as usual.
(def ns-var (ns-intern core "*ns*" (ctx-find-ns ctx (ctx-current-ns ctx))))
(put ns-var :dynamic true)
(put (ctx :env) :ns-var ns-var)
(ns-intern core "intern"
(fn [ns-name sym-name &opt val]
(def ns (ctx-find-ns ctx (if (struct? ns-name) (ns-name :name) ns-name)))
@ -969,7 +975,7 @@
(keyword? x) (string x)
nil)))
(def ns-of (fn [x]
(if (and (table? x) (not (nil? (x :mappings))))
(if (= :jolt/namespace (get x :jolt/type))
x
(let [nm (ns-name-of x)]
(if nm (get (get (ctx :env) :namespaces) nm) nil)))))

View file

@ -509,9 +509,13 @@
(get (ctx :env) :current-ns))
(defn ctx-set-current-ns
"Set the current namespace symbol."
"Set the current namespace symbol. Also keeps the *ns* dynamic var's root in
sync (the var table is cached on the env by install-stateful-fns! — one table
put on this hot path, no ns lookup chain)."
[ctx ns-sym]
(put (ctx :env) :current-ns ns-sym))
(put (ctx :env) :current-ns ns-sym)
(when-let [v (get (ctx :env) :ns-var)]
(put v :root (ctx-find-ns ctx ns-sym))))
(defn all-ns
"Return a list of all namespaces in the context."

View file

@ -0,0 +1,16 @@
# Specification: *ns* — the current-namespace dynamic var (stage 3).
# *ns* holds the current NAMESPACE OBJECT; (str *ns*) is its name; it tracks
# in-ns at the top level and works with the ns-introspection fns.
(use ../support/harness)
(defspec "*ns* / identity & printing"
["str of *ns*" "\"user\"" "(str *ns*)"]
["ns-name of *ns*" "(quote user)" "(ns-name *ns*)"]
["*ns* is find-ns" "true" "(= (ns-name *ns*) (ns-name (find-ns (quote user))))"]
["*ns* not a map" "false" "(map? *ns*)"]
["tracks in-ns" "\"jolt.test-ns-a\"" "(do (in-ns (quote jolt.test-ns-a)) (str *ns*))"]
["in-ns returns ns" "\"jolt.test-ns-b\"" "(str (in-ns (quote jolt.test-ns-b)))"]
["usable with ns fns" "true"
"(do (require (quote clojure.string)) (alias (quote nsv) (quote clojure.string)) (some? (get (ns-aliases *ns*) (quote nsv))))"]
["ns-unalias via *ns*" "true"
"(do (require (quote clojure.string)) (alias (quote nsw) (quote clojure.string)) (ns-unalias *ns* (quote nsw)) (nil? (get (ns-aliases *ns*) (quote nsw))))"])