jolt/test/integration/namespace-test.janet
Yogthos 703a59d40b core: close the compile-path gaps that broke uberscripting Selmer + config
Loading these libs via require worked (load-ns-source interprets, macros
expand lazily) but the same code inlined by uberscript routes through
eval-toplevel and compiled, surfacing four gaps:

- a ^{:map} metadata def name reads as (def (with-meta name m) v); the
  analyzer died extracting the name (config.core's defonce env). It now
  throws uncompilable so the interpreter, which handles it, takes over.
- declare was a no-op, so a compiled forward reference to a declared
  name that collides with a janet root binding bound to the host fn
  (selmer.parser's (declare parse) compiled to janet's 1-arg parse).
  declare now expands to no-init defs, the interpreter interns them,
  and the analyzer routes no-init def to the interpreter.
- class? was missing (selmer.util's exception macro calls it at
  expansion time). Always false, like ratio? — no Class objects here.
- require of an unlocatable namespace silently left an empty ns behind,
  deferring the failure to an unresolved symbol far from the cause. It
  now throws like Clojure's FileNotFoundException. Namespaces entered
  in-session count as loaded (Clojure puts them in *loaded-libs*), and
  the SCI bootstrap opts out via :lenient-require? since its
  clj-targeted requires can't all exist on this host.
2026-06-11 01:31:50 -04:00

119 lines
4.7 KiB
Text

(use ../../src/jolt/reader)
(use ../../src/jolt/types)
(use ../../src/jolt/evaluator)
(import ../../src/jolt/api :as api)
# ns/in-ns/require/use are overlay macros + clojure.core fns now (Stage 2 jolt-eaa),
# so these interpreter tests need the full env (init loads the overlay + installs
# the stateful fns), not a bare make-ctx.
(defn- fresh-ctx [] (api/init))
# Helper: parse and eval in a fresh ctx
(defn eval-str [s]
(let [ctx (fresh-ctx)
form (parse-string s)]
(eval-form ctx @{} form)))
(print "1: in-ns...")
(let [ctx (fresh-ctx)]
(def form (parse-string "(in-ns 'my.app)"))
(eval-form ctx @{} form)
(assert (= "my.app" (ctx-current-ns ctx)) "in-ns switches namespace"))
(print " passed")
(print "2: def in different namespace...")
(let [ctx (fresh-ctx)]
(eval-form ctx @{} (parse-string "(in-ns 'my.app)"))
(eval-form ctx @{} (parse-string "(def x 42)"))
(let [ns (ctx-find-ns ctx "my.app")
v (ns-find ns "x")]
(assert (= 42 (var-get v)) "def works in new namespace")))
(print " passed")
(print "3: ns form...")
(let [ctx (fresh-ctx)]
(eval-form ctx @{} (parse-string "(ns my.lib)"))
(assert (= "my.lib" (ctx-current-ns ctx)) "ns sets current namespace"))
(print " passed")
(print "4: ns with require...")
(let [ctx (fresh-ctx)]
# Set up a namespace with some vars
(let [other-ns (ctx-find-ns ctx "other.lib")]
(ns-intern other-ns "f" (fn [x] (inc x))))
# Now ns with require
(eval-form ctx @{} (parse-string "(ns my.app (:require [other.lib :as o]))"))
# current-ns should be my.app
(assert (= "my.app" (ctx-current-ns ctx)) "ns with require sets current namespace")
# Alias should be registered
(let [ns (ctx-find-ns ctx "my.app")
aliased (ns-alias-lookup ns "o")]
(assert (= "other.lib" aliased) "alias o -> other.lib registered")))
(print " passed")
(print "5: require form (standalone)...")
(let [ctx (fresh-ctx)]
# Populate other.lib first — require of an unlocatable ns now throws
# (Clojure's FileNotFoundException), see namespaces-spec.
(let [other-ns (ctx-find-ns ctx "other.lib")]
(ns-intern other-ns "f" (fn [x] x)))
(eval-form ctx @{} (parse-string "(require '[other.lib :as o])"))
(let [ns (ctx-find-ns ctx "user")
aliased (ns-alias-lookup ns "o")]
(assert (= "other.lib" aliased) "standalone require registers alias")))
(print " passed")
(print "6: qualified symbol via alias...")
(let [ctx (fresh-ctx)]
# Set up target ns
(let [target (ctx-find-ns ctx "other.lib")]
(ns-intern target "f" (fn [x] (inc x))))
# Register alias
(let [ns (ctx-find-ns ctx "user")]
(ns-import ns "o" "other.lib"))
# Resolve o/f and call it
(let [form (parse-string "(o/f 41)")
result (eval-form ctx @{} form)]
(assert (= 42 result) "qualified call via alias works")))
(print " passed")
(print "7: require then use alias...")
(let [ctx (fresh-ctx)]
# Set up target ns
(let [target (ctx-find-ns ctx "math.lib")]
(ns-intern target "add" (fn [a b] (+ a b))))
# require + use
(eval-form ctx @{} (parse-string "(require '[math.lib :as m])"))
(let [result (eval-form ctx @{} (parse-string "(m/add 1 2)"))]
(assert (= 3 result) "require + alias + call chain works")))
(print " passed")
(print "8: ns form requires multiple...")
(let [ctx (fresh-ctx)]
(let [ns1 (ctx-find-ns ctx "a.lib")]
(ns-intern ns1 "f" (fn [x] (inc x))))
(let [ns2 (ctx-find-ns ctx "b.lib")]
(ns-intern ns2 "g" (fn [x] (dec x))))
(eval-form ctx @{} (parse-string "(ns user (:require [a.lib :as a] [b.lib :as b]))"))
(assert (= 43 (eval-form ctx @{} (parse-string "(a/f 42)"))) "alias a works")
(assert (= 41 (eval-form ctx @{} (parse-string "(b/g 42)"))) "alias b works"))
(print " passed")
(print "\nAll namespace tests passed!")
# An UNCAUGHT throw inside an interpreted fn body must restore the caller's
# current-ns (the body runs with current-ns rebound to the defining ns; the
# restore is a defer now). Pre-fix, the ctx was left stuck in the defining ns
# and every later alias-qualified lookup failed — the sci-bootstrap and
# clojure.edn "Unable to resolve alias/..." cascades.
(print "ns restored after uncaught throw...")
(let [ctx (fresh-ctx)]
(api/eval-string ctx "(in-ns (quote otherns))")
(api/eval-string ctx "(clojure.core/refer-clojure)")
(api/eval-string ctx "(defn boom [] (throw (ex-info \"x\" {})))")
(api/eval-string ctx "(in-ns (quote user))")
(assert (not ((protect (api/eval-string ctx "(otherns/boom)")) 0)) "boom throws")
(assert (= "user" (api/eval-string ctx "(str *ns*)")) "*ns* restored after uncaught throw")
(api/eval-string ctx "(require (quote [clojure.string :as s9]))")
(assert (= "A" (api/eval-string ctx "(s9/upper-case \"a\")")) "aliases still resolve"))
(print " ok")