Skip to content

Commit 7533772

Browse files
authored
Fixed a bug where Basilisp was unable to identify references to Vars in Namespaces whose aliase matches imported Python modules (#490)
* Fixed a bug where Basilisp was unable to identify references to Vars in Namespaces whose aliase matches imported Python modules * Fix direct linking * Refactor the Var linking code * Require with imports * Some stuff * Ok apparently the entire require system was wrong * Lots of changes not all of which go together * MyPy fixes * Remove unused slot * Bad * Check the number of elements in a quoted form * Honestly what the fuck is going on * No need to save the CompilerContext * Remove unused import * What does this do? * Clean it up * Change the name * Clean it up a bit * Parameterize Import tests * Broken tests * Runtime re-org * Require tests * Stop using Symbol constructor directly * Type assertion from updated MyPy version * Remove unused aliases * Remove unused Analyzer code * Remove unused branches * one more thing
1 parent 190ee7b commit 7533772

File tree

11 files changed

+581
-235
lines changed

11 files changed

+581
-235
lines changed

src/basilisp/cli.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,12 @@ def eval_str(s: str, ctx: compiler.CompilerContext, ns: runtime.Namespace, eof:
5252
return last
5353

5454

55-
def bootstrap_repl(which_ns: str) -> types.ModuleType:
55+
def bootstrap_repl(ctx: compiler.CompilerContext, which_ns: str) -> types.ModuleType:
5656
"""Bootstrap the REPL with a few useful vars and returned the bootstrapped
5757
module so it's functions can be used by the REPL command."""
58-
repl_ns = runtime.Namespace.get_or_create(sym.symbol(REPL_NS))
5958
ns = runtime.Namespace.get_or_create(sym.symbol(which_ns))
60-
core_ns = runtime.Namespace.get(sym.symbol(runtime.CORE_NS))
61-
assert core_ns is not None
62-
ns.refer_all(core_ns)
63-
repl_module = importlib.import_module(REPL_NS)
64-
ns.add_alias(repl_ns, sym.symbol(REPL_NS))
65-
ns.refer_all(repl_ns)
66-
return repl_module
59+
eval_str(f"(ns {sym.symbol(which_ns)} (:use basilisp.repl))", ctx, ns, object())
60+
return importlib.import_module(REPL_NS)
6761

6862

6963
@cli.command(short_help="start the Basilisp REPL")
@@ -108,7 +102,6 @@ def repl(
108102
warn_on_var_indirection,
109103
):
110104
basilisp.init()
111-
repl_module = bootstrap_repl(default_ns)
112105
ctx = compiler.CompilerContext(
113106
filename=REPL_INPUT_FILE_PATH,
114107
opts={
@@ -118,6 +111,7 @@ def repl(
118111
compiler.WARN_ON_VAR_INDIRECTION: warn_on_var_indirection,
119112
},
120113
)
114+
repl_module = bootstrap_repl(ctx, default_ns)
121115
ns_var = runtime.set_current_ns(default_ns)
122116
prompter = get_prompter()
123117
eof = object()
@@ -216,7 +210,7 @@ def run( # pylint: disable=too-many-arguments
216210
)
217211
eof = object()
218212

219-
core_ns = runtime.Namespace.get(sym.symbol(runtime.CORE_NS))
213+
core_ns = runtime.Namespace.get(runtime.CORE_NS_SYM)
220214
assert core_ns is not None
221215

222216
with runtime.ns_bindings(in_ns) as ns:

src/basilisp/core.lpy

Lines changed: 165 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3190,11 +3190,13 @@
31903190

31913191
(defn eval
31923192
"Evaluate a form (not a string) and return its result."
3193-
[form]
3194-
(let [ctx (basilisp.lang.compiler.CompilerContext. "<Eval Input>")]
3195-
(basilisp.lang.compiler/compile-and-exec-form form
3196-
ctx
3197-
*ns*)))
3193+
([form]
3194+
(eval form *ns*))
3195+
([form namespace]
3196+
(let [ctx (basilisp.lang.compiler.CompilerContext. "<Eval Input>")]
3197+
(basilisp.lang.compiler/compile-and-exec-form form
3198+
ctx
3199+
namespace))))
31983200

31993201
;;;;;;;;;;;;;;;;;;;
32003202
;; Ref Utilities ;;
@@ -3399,50 +3401,137 @@
33993401
[module]
34003402
`(import* ~module))
34013403

3404+
(import importlib)
3405+
3406+
(defn- require-libspec
3407+
"Convert a user-specified require libspec into a map with well-defined keys.
3408+
3409+
Required keys:
3410+
- :namespace ns-sym, a symbol naming the Namespace being required
3411+
3412+
Optional keys:
3413+
- :as ns-alias, a symbol which will alias the Namespace when required (if given)
3414+
- :refer [sym1, sym2], a sequence of symbols naming Vars to refer
3415+
- :refer :all, if every Var should be referred
3416+
- :only [sym1, sym2], a sequence of symbols naming Vars to refer
3417+
- :exclude [sym1, sym2], to refer all except the specified symbols
3418+
- :rename {sym1 new-sym1}, to rename all the specified symbols to the given new name"
3419+
[req]
3420+
(cond
3421+
(vector? req) (merge {:namespace (first req)}
3422+
(apply hash-map (rest req)))
3423+
(symbol? req) {:namespace req}
3424+
:else (throw
3425+
(ex-info "Invalid libspec for require"
3426+
{:value req}))))
3427+
3428+
(defn- require-lib
3429+
"Require the library described by `libspec` into the Namespace `requiring-ns`."
3430+
[requiring-ns libspec]
3431+
(let [required-ns-sym (:namespace libspec)]
3432+
;; In order to enable direct linking of Vars as Python variables, required
3433+
;; namespaces must be `require*`ed into the namespace. That's not possible
3434+
;; to do without a macro, so we're using this hacky approach to eval the
3435+
;; code directly (which will effectively add it to the root namespace module).
3436+
(eval (list 'require*
3437+
(if-let [ns-alias (:as libspec)]
3438+
[required-ns-sym :as ns-alias]
3439+
required-ns-sym))
3440+
requiring-ns)
3441+
;; Reset the namespace to the requiring namespace, since it was likely changed
3442+
;; during the require process
3443+
(set! *ns* requiring-ns)))
3444+
3445+
(defn- refer-filtered-interns
3446+
"Return a map of symbols to interned Vars in the Namespace `referred-ns` subject
3447+
to the filters described in `libspec`."
3448+
[referred-ns libspec]
3449+
(when (= :all (:refer libspec))
3450+
(throw
3451+
(ex-info "cannot specify :refer :all for refer; use (refer 'ns-sym) instead"
3452+
{:libspec libspec})))
3453+
(let [only (set (or (:refer libspec) (:only libspec)))
3454+
exclude (set (:exclude libspec))
3455+
rename (get libspec :rename {})]
3456+
(cond->> (ns-interns referred-ns)
3457+
(seq only) (filter (fn [entry]
3458+
(contains? only (key entry))))
3459+
(seq exclude) (remove (fn [entry]
3460+
(contains? exclude (key entry))))
3461+
(seq rename) (reduce (fn [m entry]
3462+
(if (rename (key entry))
3463+
(assoc m (rename (key entry)) (val entry))
3464+
m))
3465+
{}))))
3466+
3467+
(defn- refer-lib
3468+
"Refer Vars into `requiring-ns` as described by `libspec`.
3469+
3470+
This function assumes the referred-to Namespace has already been loaded by
3471+
`require-lib`."
3472+
[requiring-ns libspec]
3473+
(let [referred-ns (the-ns (:namespace libspec))]
3474+
(if-not (some #(contains? libspec %) [:refer :only :exclude :rename])
3475+
(.refer-all requiring-ns referred-ns)
3476+
(doseq [intern-entry (refer-filtered-interns referred-ns libspec)]
3477+
(let [sym (key intern-entry)
3478+
var (val intern-entry)]
3479+
(.add-refer requiring-ns sym var))))))
3480+
3481+
(defn require
3482+
"Load Basilisp libraries and make them accessible in the current namespace.
3483+
3484+
Arguments should be libspecs, which take the following forms:
3485+
- symbols, which name fully qualified namespaces
3486+
- vectors, which take the form [namespace-symbol & opts]
3487+
3488+
Vector libspec arguments must be one of:
3489+
- :as name, which will alias the imported namespace to the symbol name
3490+
- :refer [& syms], which will refer syms in the local namespace directly
3491+
- :refer :all, which will refer all symbols from the namespace directly
3492+
3493+
Use of `require` directly is discouraged in favor of the `:require` directive in
3494+
the `ns` macro."
3495+
[& args]
3496+
(let [current-ns *ns*]
3497+
(doseq [libspec (map require-libspec args)]
3498+
(require-lib current-ns libspec)
3499+
3500+
;; Add refers
3501+
(let [new-ns (the-ns (:namespace libspec))
3502+
refer-opt (:refer libspec)]
3503+
(cond
3504+
(= :all refer-opt)
3505+
(.refer-all current-ns new-ns)
3506+
3507+
(seq refer-opt)
3508+
(let [new-ns-interns (ns-interns new-ns)]
3509+
(doseq [var-sym refer-opt]
3510+
(let [var (get new-ns-interns var-sym)]
3511+
(.add-refer current-ns var-sym var))))
3512+
3513+
:else nil)))
3514+
nil))
3515+
34023516
(defn refer
34033517
"Refer Vars from the namespace named by ns-sym, subject to the filters specified.
34043518

34053519
Supported filters:
34063520
- :only [sym1 sym2], to only refer the specified symbols
34073521
- :exclude [sym1, sym2], to refer all except the specified symbols
3408-
- :rename {sym1 new-sym1}, to rename all the specified symbols to the given new name"
3522+
- :rename {sym1 new-sym1}, to rename all the specified symbols to the given new name
3523+
3524+
Use of `refer` directly is discouraged in favor of the `:refer` modifier in the
3525+
`:require` directive of the `ns` macro or the `:use` directive of the `ns` macro."
34093526
[ns-sym & filters]
3410-
(let [current-ns *ns*
3411-
ns (the-ns ns-sym)
3412-
filter-map (apply hash-map filters)]
3413-
(if-not (seq filters)
3414-
(.refer-all current-ns ns)
3415-
(let [only (set (:only filter-map))
3416-
exclude (set (:exclude filter-map))
3417-
rename (or (:rename filter-map) {})
3418-
3419-
interns (if (seq only)
3420-
(filter (fn [entry]
3421-
(contains? only (key entry)))
3422-
(ns-interns ns))
3423-
(ns-interns ns))
3424-
interns (if (seq exclude)
3425-
(remove (fn [entry]
3426-
(contains? exclude (key entry)))
3427-
interns)
3428-
interns)
3429-
interns (if (seq rename)
3430-
(reduce (fn [m entry]
3431-
(if (rename (key entry))
3432-
(assoc m (rename (key entry)) (val entry))
3433-
m))
3434-
{}
3435-
interns)
3436-
interns)
3437-
3438-
do-refer (fn [refers]
3439-
(when (seq refers)
3440-
(let [entry (first (seq refers))
3441-
sym (key entry)
3442-
var (val entry)]
3443-
(.add-refer current-ns sym var)
3444-
(recur (rest refers)))))]
3445-
(do-refer interns)))))
3527+
(let [current-ns *ns*]
3528+
;; It is necessary to first require the Namespace directly, otherwise
3529+
;; when refer attempts to load the Namespace later, it will fail.
3530+
(->> (require-libspec ns-sym)
3531+
(require-lib current-ns))
3532+
(->> (apply vector ns-sym filters)
3533+
(require-libspec)
3534+
(refer-lib current-ns))))
34463535

34473536
(def refer-basilisp
34483537
"Refer Vars from basilisp.core using the same filter syntax as refer."
@@ -3452,67 +3541,32 @@
34523541
"Compatibility layer with JVM Clojure, which points to refer-basilisp."
34533542
refer-basilisp)
34543543

3455-
(import importlib)
3456-
3457-
(defn ^:private add-refers
3458-
"Refer all of the Vars named in syms from from-ns to to-ns."
3459-
[to-ns from-ns syms]
3460-
(when (seq syms)
3461-
(let [var-sym (first syms)
3462-
var (get (ns-interns from-ns) var-sym)]
3463-
(.add-refer to-ns var-sym var)
3464-
(recur to-ns from-ns (rest syms)))))
3465-
3466-
(defn ^:private require-vec
3467-
"Process a vector libspec for require."
3468-
[current-ns v]
3469-
(let [ns-sym (first v)
3470-
opts (apply hash-map (rest v))]
3471-
(importlib/import-module (name ns-sym))
3472-
(let [new-ns (the-ns ns-sym)]
3473-
;; Add the namespace alias
3474-
(.add-alias current-ns new-ns (or (:as opts) ns-sym))
3475-
3476-
;; Add refers
3477-
(cond
3478-
(= :all (:refer opts))
3479-
(.refer-all current-ns new-ns)
3480-
3481-
(:refer opts)
3482-
(add-refers current-ns new-ns (:refer opts))
3483-
3484-
:else nil))))
3485-
3486-
(defn ^:private require-sym
3487-
"Process a symbol libspec for require."
3488-
[s]
3489-
(importlib/import-module (name s)))
3490-
3491-
(defn require
3544+
(defn use
34923545
"Load Basilisp libraries and make them accessible in the current namespace.
34933546

34943547
Arguments should be libspecs, which take the following forms:
34953548
- symbols, which name fully qualified namespaces
34963549
- vectors, which take the form [namespace-symbol & opts]
34973550

3498-
Vector libspec arguments must be one of:
3499-
- :as name, which will alias the imported namespace to the symbol name
3500-
- :refer [& syms], which will refer syms in the local namespace directly"
3551+
`use` is like `require` which also refers all Vars from the requiring Namespace
3552+
afterwards. Libspecs passed to `use` may include filters as defined in `refer` to
3553+
narrow down the referred Vars.
3554+
3555+
Supported filters:
3556+
- :only [sym1 sym2], to only refer the specified symbols
3557+
- :exclude [sym1, sym2], to refer all except the specified symbols
3558+
- :rename {sym1 new-sym1}, to rename all the specified symbols to the given new name
3559+
3560+
Use of `use` directly is discouraged in favor of the `:use` directive in the `ns`
3561+
macro."
35013562
[& args]
3502-
(let [current-ns *ns*
3503-
3504-
do-require (fn [requires]
3505-
(when (seq requires)
3506-
(let [v (first requires)]
3507-
(cond
3508-
(vector? v) (require-vec current-ns v)
3509-
(symbol? v) (require-sym v)
3510-
:else (throw
3511-
(ex-info "Invalid libspec for require"
3512-
{:value v}))))
3513-
(recur (rest requires))))]
3514-
(do-require args)
3515-
(set! *ns* current-ns)
3563+
(let [current-ns *ns*]
3564+
(doseq [libspec (map require-libspec args)]
3565+
;; Remove the :refer key to avoid having require-lib
3566+
;; perform a full :refer, instead we'll use refer-lib
3567+
(->> (dissoc libspec :refer)
3568+
(require-lib current-ns))
3569+
(refer-lib current-ns libspec))
35163570
nil))
35173571

35183572
(defmacro ns
@@ -3529,6 +3583,8 @@
35293583
(:refer-basilisp :exclude [get])
35303584
(:require
35313585
[basilisp.string :as str])
3586+
(:use
3587+
[basilisp.set :only [intersection]])
35323588
(:import inspect))"
35333589
[name & opts]
35343590
(when-not (and (symbol? name) (nil? (namespace name)))
@@ -3548,17 +3604,22 @@
35483604
{}
35493605
opts))
35503606

3551-
requires (when (:require opts)
3552-
`(require ~@(map #(list 'quote %) (:require opts))))
3553-
imports (when (:import opts)
3554-
(map (fn [v]
3555-
`(import ~v))
3556-
(:import opts)))]
3607+
refer-filters (when-let [filters (or (:refer-basilisp opts)
3608+
(:refer-clojure opts))]
3609+
(map #(list 'quote %) filters))
3610+
requires (when (:require opts)
3611+
`(require ~@(map #(list 'quote %) (:require opts))))
3612+
uses (when (:use opts)
3613+
`(use ~@(map #(list 'quote %) (:use opts))))
3614+
imports (when (:import opts)
3615+
(map (fn [v]
3616+
`(import ~v))
3617+
(:import opts)))]
35573618
`(do
35583619
(in-ns (quote ~name))
3559-
(refer-basilisp ~@(or (:refer-basilisp opts)
3560-
(:refer-clojure opts)))
3620+
(refer-basilisp ~@refer-filters)
35613621
~requires
3622+
~uses
35623623
~@imports)))
35633624

35643625
;;;;;;;;;;;;;;;;;;;;;

src/basilisp/importer.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -335,14 +335,6 @@ def exec_module(self, module):
335335
logger.debug(f"Failed to load cached Basilisp module: {e}")
336336
self._exec_module(fullname, spec.loader_state, path_stats, ns)
337337

338-
# Because we want to (by default) add 'basilisp.core into every namespace by default,
339-
# we want to make sure we don't try to add 'basilisp.core into itself, causing a
340-
# circular import error.
341-
#
342-
# Later on, we can probably remove this and just use the 'ns macro to auto-refer
343-
# all 'basilisp.core values into the current namespace.
344-
runtime.Namespace.add_default_import(ns_name)
345-
346338

347339
def hook_imports():
348340
"""Hook into Python's import machinery with a custom Basilisp code

0 commit comments

Comments
 (0)