Cherry/Squint fails to export macro-generated functions
version
cherry 0.4.30 with deps cherry compiler
problem
This affects any ClojureScript library using macros for code generation, making it impossible to create a public API through macro-generated functions.
repro
(ns macro-export-test)
;; A simple macro that generates a function
(defmacro generate-function
"Macro that generates a function definition"
[fn-name return-value]
`(defn ~fn-name []
~return-value))
;; Explicitly defined function - this WILL be exported
(defn explicit-function []
"I am explicitly defined")
;; Use the macro to generate a function - this WILL NOT be exported
(generate-function generated-function "I am generated by a macro")
;; A more complex macro that generates multiple functions
(defmacro generate-multiple-functions
"Generates multiple function definitions"
[prefix count]
`(do
~@(for [i (range count)]
`(defn ~(symbol (str prefix "-" i)) []
~(str "Generated function " i)))))
;; Generate 3 functions - NONE of these will be exported
(generate-multiple-functions my-fn 3)
;; Another explicit function to show the difference
(defn another-explicit []
"I am also explicit")
# Compile with Cherry
npx cherry compile macro_export_test.cljc
# Check what's exported
grep "export {" macro_export_test.mjs
expected behavior
Actual output
export { generate_function, explicit_function, generate_multiple_functions, another_explicit }
Expected output
export { explicit_function, generated_function, my_fn_0, my_fn_1, my_fn_2, another_explicit }
Potential reason:
(defmethod emit-special 'def [_type env [_const & more :as expr]]
(let [name (first more)]
(when-not (:private (meta name))
(swap! *public-vars* conj (munge* name)))
...))
Since def forms in macro expansion happen after this, forms generated by macros are never added to public-vars.
In cherry and squint, macros need to be placed in a separate .cljc file and macros need to be required with :require-macros in normal code files.
Right. I saw that briefly but assumed I could get around it inline. Will give it a shot. Sorry for the noise!
Conceptually this solution makes sense, but it appears broken at the moment.
➜ cherry-test cat package.json
{
"name": "cherry-test",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"cherry-cljs": "^0.4.30"
}
}
➜ cherry-test cat macros.cljs
(ns macros)
(defmacro do-twice [x]
`(try (do ~x ~x)
(finally (prn :done!))))
➜ cherry-test cat macro_usage.cljs
(ns macro-usage
(:require-macros ["./macros.mjs" :refer [do-twice]]))
(do-twice (prn :hello))
➜ cherry-test npx cherry macros.cljs
[cherry] Compiling CLJS file: macros.cljs
[cherry] Wrote file: /Users/wcohen/programming/clj/cherry-test/macros.mjs
➜ cherry-test npx cherry macro_usage.cljs
[cherry] Compiling CLJS file: macro_usage.cljs
node:fs:441
path = getValidatedPath(path);
^
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL. Received null
at Module.readFileSync (node:fs:441:14)
at file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/compiler.sci.js:1034:492
at file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/compiler.sci.js:125:503
at bha (file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/compiler.sci.js:126:4)
at tU.l (file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/compiler.sci.js:514:321)
at tU.o (file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/compiler.sci.js:514:449)
at $APP.Ph.A (file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/cljs.core.js:347:405)
at uU (file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/compiler.sci.js:130:234)
at AV.l (file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/compiler.sci.js:515:205)
at AV.o (file:///Users/wcohen/programming/clj/cherry-test/node_modules/cherry-cljs/lib/compiler.sci.js:515:286) {
code: 'ERR_INVALID_ARG_TYPE'
}
Node.js v23.5.0
It appears that this syntax from corpus is out of date. Adjusting to (:require-macros [macros :refer [do-twice]]) works.
Yeah that is how it should be done. PR welcome.