ClojureScript, macros and type hints

Not sure if this is possible but is there a way to use the ^js type hint in a clojurescript macro? This is using non self hosted clojurescript. So the macro is written in clojure so doesn’t have access to cljs as far as I can tell.

(defmacro texture-for
  "Throws compile time error if k is not a valid sprite key."
  [app k]
  (let [resolved-k (if (symbol? k) @(resolve 'k) k)]
    (assert (sprite-keys resolved-k)
            (str resolved-k " is not a valid sprite key.")))
  (let [k-string (-> k name str)]
    `(let [^js r# (-> ~app .-loader .-resources (aget "resources/spritesheet.json"))]
       (-> r# .-textures (aget ~k-string)))))

The type hint does get added, it just resolves it to the macro namespace so ends up as ^foo/js rather than staying as ^js. Not sure if there’s away round this as I’m new to clojurescript. If I could find a way to keep the type hint as ^js then is might work is my current reasoning? :sweat_smile:

Any help would be greatly appreciated.

Easiest way is not using r# but instead creating the symbol with the proper metadata yourself.

For example:

(defmacro texture-for
  "Throws compile time error if k is not a valid sprite key."
  [app k]
  (let [resolved-k (if (symbol? k) @(resolve 'k) k)]
    (assert (sprite-keys resolved-k)
      (str resolved-k " is not a valid sprite key.")))
  (let [k-string (-> k name str)
        r (with-meta (gensym "r") {:tag 'js})]
    `(let [~r (-> ~app .-loader .-resources (aget "resources/spritesheet.json"))]
       (-> ~r .-textures (aget ~k-string)))))

But maybe a better solution is moving the access logic out of the macro completely and writing a helper function to handle that. I’m unsure what the macro does exactly since the resolved-k is thrown away and not actually used.

1 Like

That seems to work! Thanks again. :partying_face:

You’re right. Naive question, but can you reference clojurescript helper functions in a clojure macro? Or would you need to use cljc?

It just fails at compile time if the key is not in the set. Handy for when you have loads of assets in a spritesheet and typing a name wrong means the asset not loading at runtime and you only discovering it once you get to that part of the level.

(sprites/texture-for app :wal_left) -> :wal_left is not a valid sprite key.

Probably overkill, but it was a good excuse to get more familiar with clojurescript macros.

PS: Also thanks for writing this article it was really helpful.

1 Like

Yes, that is not problem as long as that funciton is called at runtime by the emitted code and not directly in the macro. Just use the fully qualified name of the helper function that does not rely on any aliases. (your.lib.sprites/texture-for* ....) instead of (alias/texture-for* ...).

(defmacro texture-for
  "Throws compile time error if k is not a valid sprite key."
  [app k]
  (let [resolved-k (if (symbol? k) @(resolve 'k) k)]
    (assert (sprite-keys resolved-k)
      (str resolved-k " is not a valid sprite key.")))
  (let [k-string (-> k name str)]
    `(full.qualified/texture-for* ~app ~k-string)))

Also makes the macro generate less code so always worth doing this if you can.

1 Like