squint icon indicating copy to clipboard operation
squint copied to clipboard

Cherry/Squint fails to export macro-generated functions

Open willcohen opened this issue 5 months ago • 5 comments

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.

willcohen avatar Jul 17 '25 14:07 willcohen

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.

borkdude avatar Jul 17 '25 14:07 borkdude

Right. I saw that briefly but assumed I could get around it inline. Will give it a shot. Sorry for the noise!

willcohen avatar Jul 17 '25 14:07 willcohen

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

willcohen avatar Jul 17 '25 17:07 willcohen

It appears that this syntax from corpus is out of date. Adjusting to (:require-macros [macros :refer [do-twice]]) works.

willcohen avatar Jul 17 '25 17:07 willcohen

Yeah that is how it should be done. PR welcome.

borkdude avatar Jul 17 '25 18:07 borkdude