clojure-dev

dmiller 2025-02-19T00:09:54.918029Z

Comparing Compiler.DefExpr.eval() to Compiler.DefExpr.emit() The sequence of operations in eval: 1. (optionally) set the Var root value 2. (optionally) set the Var's meta 3. Set isDynamic appropriately The sequence of operations in emit: 1. Set isDynamic appropriately 2. (optionally) set the Var's meta 3. (optionally) set the Var root value The different placement of the isDynamic code is not a problem. The different order of the other two options in emit seems to be problematic. Setting the Var's root value is done by call to Var.bindRoot. This method removes the :macro key. So if one sets the meta, then binds the root, the value for :macro in the meta is lost. Because DefExpr handling usually short-circuits through eval, perhaps just never happens. (Unless you are playing games with the compiler, in which case one might lose six hours trying to figure out some mysterious incorrect behavior.) I'm just curious if my analysis is correct. (changing the order did cause the mysterious incorrect behavior to disappear.) (These methods also contain one of my favorite lines: if (initProvided || true)//includesExplicitMetadata((MapExpr) meta)) which I have copied in my code out of loyalty to the source. 🙂 From my last post and this, you might conclude I have an obsession with boolean expressions.)

👀 1
Alex Miller (Clojure team) 2025-03-04T22:00:19.523489Z

can you give an example of the problematic code?

Alex Miller (Clojure team) 2025-03-04T22:03:23.168199Z

could just be a bug

dmiller 2025-03-05T05:14:26.225829Z

It won't happen to normal people living a real life. I'm emulating the start of core.clj. I don't have defmacro defined, so I have to do it by hand. Source code:

(ns test.macro)
(def ^{:macro true} m1 (fn* [&form &env & decl] `~decl))
Loading source code, no compilation yet.
(load "test/macro")        ; => nil
(in-ns 'test.macro)        ; => ...namespace...
(meta #'m1)                ; => {:macro true, :line 3, :column 1, :file "test/macro.clj", :name m1,  ... etc...]}
(macroexpand-1 '(m1 a b))  ; => (a b)
All cool. Now compile and try again.
(load "test/macro")        ; => nil
(in-ns 'test.macro)        ; => ...namespace...
(meta #'m1)                ; => {:line 3, :column 1, ...]}  // no :macro flag
(macroexpand-1 '(m1 a b))  ; =>  (m1 a b)                   // not a macro, for sure
One could just say "don't do that". One could say "use defmacro, you fool". defmacro solves it by setting the macro flag on the var after the def does its business, so you don't notice. I'm not sure how compiling core.clj manages. There are three macros defined by the method illustrated before defmacro is defined. They are all defined as :redef true -- they get redefined via defmacro later.

dmiller 2025-03-05T12:18:11.886649Z

I now remember how compiling core.clj works. During compilation, every form is has written to file (either .class files or into the init method), but also an .eval() is done. So while doing the compilation we are using eval'd versions of the first three macro definitions, which are correct. When Clojure starts up for real, the .setMacro calls are in there.