Welcome! Please see the About page for a little more info on how this works.

0 votes
in ClojureScript by

I'm working in clojurescript, where ns-interns is a macro.

I'm trying to write a macro that gets information about the internal bindings in a namespace, but I'm not able to manipulate the bindings at compile time.

(defmacro my-macro [namespace]
   (let [mappings (ns-interns namespace)]
           ; do stuff

(my-macro 'mylib.hi)

The trouble is that the above fails because the ns-interns macro think it's processing the symbol namespace, and not the namespace I pass into my-macro.

I can try

[mappings `(ns-interns ~namespace)]

, but then I can't expand the macro at compile-time into a map that I can use to generate code -- the only thing I can do is "print" it in the generated code. I tried using macroexpand and macroexpand-all, but they did not work for me -- I couldn't use them to get the results of ns-interns for use in my macro.

However, macros can be expanded, since the below will work if I hard-code the namespace symbol. The trouble comes because I want to pass the outer macro's argument into an inner macro. What's the right way to solve approach this, where I want to get and manipulate the vars of a namespace at compile-time in Clojurescript?

(defmacro my-macro [namespace]
   (let [mappings (ns-interns 'mylib.hi)]
     ; do stuff

2 Answers

0 votes
edited by

I think this is impossible. The closest I was able to get was something like

(defmacro example-macro [ns]
  (let [x (list 'cljs.core/ns-interns ns)
    `(defmacro ~(symbol "example") []
       (let [y# ~x]

And then call (example-macro 'my.ns) from some other namespace to generate the inner macro. But then Clojure gives me an error about how it can't resolve the vars from the namespace that I passed in. I assume that means that in the inner macro, it successfully expanded ns-interns to get the symbol-var mappings, but then further steps in compiling kill it.

So I'm convinced now that in Clojurescript, even at compile-time, you can't get any data about namespaces and use it to generate code.

0 votes

In CLJS the vars are not reified directly in the runtime. In macros you can get them from the analyzer data.

You can find and example of how thats done in the ns-interns macro itself. The key line you want in your macro being (get-in @env/*compiler* [:cljs.analyzer/namespaces ns :defs]).

The :defs is a map of symbols to their def data (eg. name, metadata, ...).

Building on top of ns-interns itself will not work but using what it does internally will let you access the data and generate what you want.


Do you have quick access to a working example of something like this from *outside* the compiler project?  When I tried testing out `env/*compiler*` in macros in both a .clj and a .cljc file (using shadow-cljs), I got errors like "Could not compile clojure.lang.Atom to a ClojureScript constant", or it would evaluate to `nil`. When I tried testing `@env/*compiler*`, the compiler would report a Null Pointer error.

Since [env.cljc](https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/env.cljc) says that `env/*compiler* is functionally  private to the compiler, I thought I was barking up the wrong tree -- like the var wasn't bound when my macro was trying to evaluate it.

If no link, I'd appreciate any thoughts on what the compiler errors mean for what counts as "valid code" in the macro.


For example, even when requiring `cljs.env` and `cljs.compiler` in a .cljc file, expanding this has `x` evaluating to nil:

(core/defmacro get-ns-interns [namespace]
  (core/let [x env/*compiler* ]
    `(let []
You need to get the data out of the atom and generate whatever code you need with that data. Same as the `ns-interns` macro really, there is no difference in how you do it inside or outside the compiler project. If you want to stay on the "safe side" use things from the `cljs.analyzer.api` namespace instead of digging directly into the atom, but doing so is fine IMHO.