Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

failure when try to define and subsequently call a macro in a try block #1086

Open
ikappaki opened this issue Oct 13, 2024 · 4 comments
Open
Labels
component:compiler Issue pertaining to compiler issue-type:bug Something isn't working

Comments

@ikappaki
Copy link
Contributor

Hi,

it doesn't seem to be possible to define and subsequently call a macro inside a try block

error occurred during macroexpansion: 'Unbound' object is not callable

To reproduce,
open the nREPL and define and call a trivial macro inside a try block, it fails with the above error

> basilisp repl
basilisp.user=> (try
                  (defmacro abc []
                    '(println 5))
                  (abc)

                  (catch Exception e
                    e))


  exception: <class 'TypeError'> from <class 'basilisp.lang.compiler.exception.CompilerException'>
      phase: :macroexpansion
    message: error occurred during macroexpansion: 'Unbound' object is not callable
       form: (abc)
   location: <REPL Input>:4

The same works fine at the top level

> basilisp repl
basilisp.user=> (defmacro abc []
                    '(println 5))
                  (abc)
5
nil

Is there a technical constraint perhaps that prevents macros from being defined anywhere else other than the top level?

Thanks

@chrisrink10
Copy link
Member

Yes, try blocks are evaluated as a single compilation unit. I see that this works in Clojure though I'm not sure how. I do not expect this is something that will be easily fixed.

@chrisrink10 chrisrink10 added issue-type:bug Something isn't working component:compiler Issue pertaining to compiler labels Oct 13, 2024
@chrisrink10
Copy link
Member

I'm confused why this issue and issues like it keep coming up. This is not like any code I find myself typically writing so can you give me some context as to why such dynamic code is necessary?

@ikappaki
Copy link
Contributor Author

I'm confused why this issue and issues like it keep coming up. This is not like any code I find myself typically writing so can you give me some context as to why such dynamic code is necessary?

Thanks for taking a look. I'm writing a macro for remote Basilisp code execution in another process and encountering errors with syntax that seems valid but throws exceptions. The macro, in its simplest form for the purpose of illustration, wraps the remote code in a try block, which can include various forms.

(defmacro with-remote-eval [& code]
  `(try
     ~@code
     (catch python/Exception e
       e)))

(with-remote-eval
  code)

I consider this a valid macro use case.

I've found a workaround for unsupported forms within try, like require, import, and now defmacro. These are moved to a top-level do form in the remote execution code, allowing the unsupported forms to work as long as they are allowed in do (which they currently are).

(defmacro with-remote-eval [& code]
  (let [[code-unsupported code-rest] (split-code code)]
    `(do
       ~@code-unsupported
       (try
         ~@code-rest
         (catch python/Exception e
           e)))))

Let me know if these errors aren't worth reporting, and I'll adjust accordingly.

Thanks

@chrisrink10
Copy link
Member

Let me know if these errors aren't worth reporting, and I'll adjust accordingly.

I would say you can keep reporting such issues as you discover them, but I didn't really understand why you were doing this and now I do so thank you.

I was just digging a bit more into this just to make sure I understood the problem. We can see it works for normal functions:

(try (defn e [] :e) (e) (catch Exception _ nil)) ;; => :e

But fails for macros as you noted. The reason for this is that the compiler generates an unbound Var when it first encounters a def (such as the one generated by defmacro). Then later in the same form it attempts to call the function (as a macro, which occurs at compile time) which is expected to be stored in that Var but it is not yet bound since the binding doesn't happen until compilation for the form is complete.

I'm not exactly sure what kind of tricks Clojure uses to allow this, but it seems unlikely to be possible in Basilisp since we need to define the function before it can be used as a macro and that can't happen until compilation is done. I don't think try forms can easily be split up the way we do with do forms either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component:compiler Issue pertaining to compiler issue-type:bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants