jolt/test/spec/namespaces-spec.janet
Yogthos e311018d55 core: one alias store (jolt-ark); '.' classified as the special form it is
require-:as wrote the string-keyed :imports table (which resolution reads)
while ns-aliases read the symbol-keyed :aliases table (which nothing wrote)
— so (ns-aliases) was always empty and the alias fn had to write both as a
bridge. :aliases (alias-name string -> ns-name string) is THE store now:
require :as and the alias fn write it, both resolution paths read it first
(falling back to :imports for class imports, which is all that table holds
now), ns-unalias removes one entry, and ns-aliases presents Clojure's
{alias-symbol -> namespace object} shape built from it. ns-resolve's
qualified path goes through the same lookup.

Also: the coverage dashboard's last 'resolvable-not-interned' entry was '.'
— which (resolve '.) returns nil for on the JVM too; the tool now classifies
it as the special form it is, and that category reads ZERO.

7 new unified-alias spec rows (require/alias/ns-unalias round-trips through
both the resolution and introspection views); the white-box namespace test
tracks the accessor rename. Gate exit 0.
2026-06-10 18:37:19 -04:00

73 lines
5.2 KiB
Text

# Specification: namespaces, vars and require.
(use ../support/harness)
(defspec "namespaces / def & vars"
["def + deref" "5" "(do (def x 5) x)"]
["def returns var" "true" "(var? (def y 1))"]
["declare then def" "2" "(do (declare z) (def z 2) z)"]
["var special form" "true" "(var? (var +))"]
["var sugar #'" "true" "(var? #'+)"]
["var-get" "5" "(do (def w 5) (var-get #'w))"]
["defn defines fn" "3" "(do (defn f [x] (inc x)) (f 2))"]
["def with docstring" "7" "(do (def d \"a doc\" 7) d)"]
["dynamic var binding" "2" "(do (def ^:dynamic *x* 1) (binding [*x* 2] *x*))"]
["binding restores" "1" "(do (def ^:dynamic *y* 1) (binding [*y* 9] nil) *y*)"]
["var-set in binding" "5" "(do (def ^:dynamic *z* 1) (binding [*z* 0] (var-set (var *z*) 5) *z*))"])
(defspec "namespaces / ns operations"
["in-ns switches" "true" "(do (in-ns 'my.ns) (symbol? 'x))"]
# ns is a macro over in-ns/require/use/import (Stage 2 jolt-eaa): the form sets
# the current ns and processes its clauses.
["ns form + alias" "\"HI\"" "(do (ns my.app (:require [clojure.string :as s])) (s/upper-case \"hi\"))"]
["ns :use refers all" "9" "(do (ns src.lib) (def helper 9) (ns dst.app (:use [src.lib])) helper)"]
["standalone use" "7" "(do (ns src.l2) (def k 7) (in-ns 'dst.a2) (use '[src.l2]) k)"]
["ns-name" "true" "(do (require (quote [clojure.string])) (= 'clojure.string (ns-name (find-ns 'clojure.string))))"]
["find-ns existing" "true" "(some? (find-ns 'clojure.core))"]
["find-ns missing" "nil" "(find-ns 'does.not.exist)"]
["resolve var" "true" "(var? (resolve '+))"]
["resolve missing" "nil" "(resolve 'totally-undefined-xyz)"])
(defspec "namespaces / require & refer"
["require :as" "\"AB\"" "(do (require '[clojure.string :as s]) (s/upper-case \"ab\"))"]
["require :refer" "true" "(do (require '[clojure.string :refer [blank?]]) (blank? \"\"))"]
["require :as + :refer" "true" "(do (require '[clojure.string :as s :refer [blank?]]) (and (blank? \"\") (= \"X\" (s/upper-case \"x\"))))"]
["require clojure.set" "#{1 2 3}" "(do (require '[clojure.set :as set]) (set/union #{1 2} #{3}))"]
["require clojure.walk" "{:a 2}" "(do (require '[clojure.walk :as w]) (w/postwalk (fn [x] (if (number? x) (inc x) x)) {:a 1}))"]
["walk keywordize-keys" "{:a 1}" "(do (require '[clojure.walk :as w]) (w/keywordize-keys {\"a\" 1}))"]
["walk stringify-keys" "true" "(do (require '[clojure.walk :as w]) (= {\"a\" 1} (w/stringify-keys {:a 1})))"])
(defspec "namespaces / alias, ns-unalias, ns-publics"
["alias + use" "\"1,2\"" "(do (require (quote clojure.string)) (alias (quote st) (quote clojure.string)) (st/join \",\" [1 2]))"]
["ns-unalias removes" "true"
"(do (require (quote clojure.string)) (alias (quote st2) (quote clojure.string)) (ns-unalias (quote user) (quote st2)) (nil? (get (ns-aliases (quote user)) (quote st2))))"]
["ns-publics has var" "true" "(do (def npv 1) (some? (get (ns-publics (quote user)) (quote npv))))"]
["newline returns nil" "nil" "(newline)"])
# A throw inside an interpreted fn body (or macro expander) must restore the
# caller's current-ns: the body runs with current-ns rebound to the fn's
# DEFINING ns, and an unwind that skipped the restore left the ctx stuck
# there — every later alias-qualified lookup in the REPL ns then failed
# ("Unable to resolve symbol: alias/...", seen via sci + clojure.edn).
(defspec "namespaces / error inside a fn must not leak its defining ns"
["alias survives a throwing stdlib call" "\"A\""
"(do (require (quote [clojure.string :as s9])) (try (s9/join nil nil nil) (catch Exception e nil)) (s9/upper-case \"a\"))"]
["*ns* restored after throw" "\"user\""
"(do (require (quote [clojure.walk :as w9])) (try (w9/postwalk nil nil nil) (catch Exception e nil)) (str *ns*))"])
# Alias bookkeeping is unified (jolt-ark): one string-keyed :aliases store,
# read by resolution AND ns-aliases (which presents Clojure's
# {alias-symbol -> namespace} shape); :imports holds class imports only.
(defspec "namespaces / unified alias store"
["require :as registers the alias" "1"
"(do (require (quote [clojure.string :as st1])) (count (filter (fn [[a n]] (= (str a) \"st1\")) (ns-aliases))))"]
["aliased call resolves" "\"A\""
"(do (require (quote [clojure.string :as st2])) (st2/upper-case \"a\"))"]
["alias fn registers + resolves" "\"B\""
"(do (require (quote [clojure.string])) (alias (quote st3) (quote clojure.string)) (st3/upper-case \"b\"))"]
["alias fn visible to ns-aliases" "true"
"(do (require (quote [clojure.string])) (alias (quote st4) (quote clojure.string)) (pos? (count (filter (fn [[a n]] (= (str a) \"st4\")) (ns-aliases)))))"]
["ns-unalias removes both views" "[0 false]"
"(do (require (quote [clojure.string :as st5])) (ns-unalias (quote user) (quote st5)) [(count (filter (fn [[a n]] (= (str a) \"st5\")) (ns-aliases))) (boolean (resolve (quote st5/upper-case)))])"]
["ns-resolve through alias" "true"
"(do (require (quote [clojure.string :as st6])) (var? (ns-resolve (quote user) (quote st6/upper-case))))"]
["empty ns-aliases is a map" "true" "(map? (ns-aliases (quote clojure.core)))"])