[Edit: TLDR; Invalid problem: you cannot substitute catch in a macro because there is no clojure.core/catch.]
Hi, I have written a macro do-catch to catch exceptions in CLJC code (`Exception` in CLJ, :default in CLJS):
(defmacro do-catch
"Generate catch expression meant to catch any exception thrown. Use
inside a try block only."
[sym & body]
(if (:ns &env) ;; :ns only exists in CLJS
`(catch :default ~sym ; catch everything, not just js/Error
~@body)
`(catch Exception ~sym ; Throwable is NOT meant to be handled
~@body)))
When I use this macro in VS-Code with Calva, I need to suppress two clj-kondo errors:
1. "Unresolved symbol" in do-catch
2. "Missing catch or finally in try" in try
I want to ask if there is a way to configure Clojure-LSP or Clj-Kondo so that I do not have to suppress errors on every use?clj-kondo hooks, though I have a feeling that the macro wouldn't work anyway as try would likely look for clauses before the macro is expanded (although I figure you are at the REPL and know if it works or not).
At any rate, if you don't care about backwards compat, with that definition you should be able to get away with tacking on the :clj-kondo/macroexpand flag that was introduced lately
I found a deeper problem, which is with the macro. Turns out that since catch is not a clojure.core/xxx var or macro, it's not substituted by the Clojure compiler, and I get a stack trace trying to use the macro. Sorry about the noise.
what you'd be better off with is a try variant
Yes, the macro must use try and catch both together. BTW, I am trying another way, which is to just expand the exception class-name for the host.
Even expanding class-name doesn't work, I need to work with the whole try-catch form at once.
got to the repl
(ns repl)
(defn ^:clj-kondo/macroexpand-hook -expand-catch-all
[env [_ bind & body]]
(assert (simple-symbol? bind))
`(catch ~(if (:ns env) :default 'java.lang.Exception) ~bind
~@body))
(defn ^:clj-kondo/macroexpand-hook -catch-all?
[form]
(and
(seq? form)
(= 'catch-all (first form))))
(defmacro ^:clj-kondo/macroexpand-hook try+
[& body]
`(try ~@(for [f body]
(if-not (-catch-all? f)
f
(-expand-catch-all &env f)))))
(try+ (throw (Exception.))
(catch-all e 3))
I'd love to use a catch-all marker var instead of testing on 'catch-all but kondo doesn't seem to enjoy itAh right, you're forcing expansion, which makes it work!
btw, the reason the marker var approach doesn't work in kondo is that (TIL) the ns in macroexpand hooks is always user ! In clj-kondo's docs I don't see an API to recover the file's ns either, but maybe @borkdude can tell us more
> the reason the marker var approach doesn't work in kondo is that (TIL) the ns in macroexpand hooks is always user
huh?
can you tel me what you mean with marker-var?
(ns repl
(:require [clj-kondo.hooks-api :as api]))
(defmacro ^:clj-kondo/macroexpand-hook catch-all [& _]
(throw (Exception. "not usable outside of try+")))
(defn ^:clj-kondo/macroexpand-hook -expand-catch-all
[env [_ bind & body]]
(assert (simple-symbol? bind))
`(catch ~(if (:ns env) :default 'java.lang.Exception) ~bind
~@body))
(defn ^:clj-kondo/macroexpand-hook -catch-all?
[form]
(and
(seq? form)
(symbol? (first form))
(if-let [v (resolve (first form))]
(and
(var? v)
(= (symbol v) `catch-all))
;; snoop into kondo's internals, would be a when-let without this clause
(throw (Exception. (str {:form (str form) :ns *ns* :catch-all `catch-all
:env (api/env)}))))))
(defmacro ^:clj-kondo/macroexpand-hook try+
[& body]
`(try ~@(for [f body]
(if-not (-catch-all? f)
f
(-expand-catch-all &env f)))))
this approach is cleaner with quasiquoting, avoids accidental capture etc.can you tell me where to look in this blob of code? can you give me some more context?
I declare catch-all as a macro and then resolve the head of the seq instead of comparing the raw symbol with 'catch-all .
This works at runtime because the ns is correct, but in the clj-kondo hook the ns is user and the resolve fails (I also tested with old-style hooks fwiw).
This is weird tbh, I remember using markers just fine in my own macros but maybe I always ended up lying to kondo and/or I have a false memory
Isn't a runtime resolve depending on where you call the macro from?
user=> (declare catch-all)
#'user/catch-all
user=> (defn foo [x] (resolve x))
#'user/foo
user=> (ns dude)
nil
dude=> (user/foo 'catch-all)
nil(ns repl2
(:require [repl]))
(repl/try+ (throw (Exception.)) (repl/catch-all e 3))
works but the file lints wrongwith our without the comment? please, no ambiguity, I find it hard enough to understand already :)
yeah I was messing it up with the if-let, anyway using a when-let and the code above it works (evals to 3)
can you maybe make a small github repo that I can clone, run + lint locally? that would help me a lot
then I can also use that to make a repro + fix the problem in kondo
sorry for trying to cut corners, should've done that in the first place
no, all good
issue fixed on master