Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
* Added functions to `basilisp.test` for using and combining test fixtures (#980)
* Added the `importing-resolve` function for dynamically importing and resolving a Python name (#1065, #1070)

### Fixed
* Fix a bug where the reader was double counting the CRLF newline seq in metadata (#1063)
Expand Down
31 changes: 30 additions & 1 deletion src/basilisp/core.lpy
Original file line number Diff line number Diff line change
Expand Up @@ -4886,7 +4886,13 @@
if the ``import`` itself occurs within a function or method.

Use of ``import`` directly is discouraged in favor of the ``:import`` directive in
the :lpy:fn:`ns` macro."
the :lpy:fn:`ns` macro.

Importing and attempting to reference an import within a top level form other than
a :lpy:form:`do` is a compile-time error because the compiler cannot verify that
the imported name exists. In cases where you may need to import and reference an
imported symbol within a single top-level form, you can use
:lpy:fn:`importing-resolve`."
[& modules]
;; We need to use eval here, because there could have been an ns
;; switch inside an eval earlier, and although *ns* is correctly set
Expand Down Expand Up @@ -5065,6 +5071,12 @@
Use of ``require`` directly is discouraged in favor of the ``:require`` directive in
the :lpy:fn:`ns` macro.

Requiring and attempting to reference a required namespace within a top level form
other than a :lpy:form:`do` is a compile-time error because the compiler cannot
verify that the required name exists. In cases where you may need to require and
reference a required symbol within a single top-level form, you can use
:lpy:fn:`requiring-resolve`.

.. warning::

Reloading namespaces has many of the same limitations described for
Expand Down Expand Up @@ -5265,6 +5277,23 @@
~uses
~@imports)))

(defn importing-resolve
"Resolve the namespaced symbol ``sym`` as a Python module member. If resolution fails,
attempts to import ``sym`` 's namespace (as by :lpy:fn:`import`\\) before resolving
again."
[sym]
(if (qualified-symbol? sym)
(let [mod-name (basilisp.lang.util/munge (namespace sym))
obj-name (basilisp.lang.util/munge (name sym))
py-resolve #(some-> (get sys/modules mod-name)
(python/getattr obj-name nil))]
(or (py-resolve)
(do (eval `(import* ~(symbol (namespace sym))) *ns*)
(py-resolve))))
(throw
(ex-info "Cannot resolve an unqualified symbol"
{:sym sym}))))

(defn requiring-resolve
"Resolve the namespaced symbol ``sym`` as by :lpy:fn:`resolve`\\. If resolution fails,
attempts to require ``sym`` 's namespace (as by :lpy:fn:`require`\\) before resolving
Expand Down
12 changes: 12 additions & 0 deletions tests/basilisp/test_core_fns.lpy
Original file line number Diff line number Diff line change
Expand Up @@ -1748,6 +1748,18 @@
;; a valid symbol in that namespace
(is (= (resolve 'basilisp.shell/sh) (requiring-resolve 'basilisp.shell/sh))))

(deftest importing-resolve-test
(is (thrown? basilisp.lang.exception/ExceptionInfo (importing-resolve 'a-symbol)))
(is (thrown? basilisp.lang.exception/ExceptionInfo (importing-resolve :a-keyword)))
(is (thrown? basilisp.lang.exception/ExceptionInfo (importing-resolve :ns.qualified/kw)))
(is (thrown? python/ImportError (importing-resolve 'not.a.real.ns/sym)))
(is (nil? (importing-resolve 'logging/fakeGetLogger)))
;; this check should succeed (in spite of the missing top-level require) because
;; the previous assertion will import 'basilisp.shell even though 'sholl is not
;; a valid symbol in that namespace
(is (= (python/getattr (get sys/modules "logging") "getLogger")
(importing-resolve 'logging/getLogger))))

(deftest ns-resolve-test
(is (= #'basilisp.core/apply (ns-resolve *ns* 'apply)))
(is (nil? (ns-resolve *ns* 'xyz)))
Expand Down