clj-kondo

2026-04-02T10:56:51.940589Z

I have a custom macro defcall that expands into a defn, but it's always variadic (in a way that isn't evident from the macro arglist). I am trying to get clj-kondo to stop bugging me about the arity. The config.edn entry is:

{:config-in-call {my.ns/defcall {:linters {:unresolved-symbol {:exclude [opts]}, :invalid-arity {:level :off}}}}, :lint-as {my.ns/defcall clojure.core/defn}}
What's wrong with that ? The :unresolved-symbol bit is working fine, and I believe it is linting as a defn. Thanks for any insights ...

borkdude 2026-04-02T10:57:43.302719Z

It would be helpful if you had any examples of call sites where clj-kondo complains and you want to silence those

2026-04-02T10:58:46.047479Z

Yeah, I'm trying to figure out how to give you that - I can't simply paste my code.

2026-04-02T11:00:18.306619Z

The calls to defcall always look something like this:

(defcall mycall
  "docs"
  {:rate-limit {:base 10 :mid 15 :max 20}}
  [ids]
  (println "hi"))

2026-04-02T11:00:38.811349Z

But then any place that mycall is called with > 1 argument, I get a clj-kondo warning:

2026-04-02T11:01:10.471159Z

src/clj/dev.clj:262:18: error: my.ns/mycall is called with 5 args but expects 1

borkdude 2026-04-02T11:01:51.956779Z

ok got it, let me try to repro this locally

2026-04-02T11:02:23.206969Z

Thank you!

borkdude 2026-04-02T11:12:06.800899Z

So given this:

(ns dude)

(defmacro defcall [] :whatever)

(defcall mycall
  "docs"
  {:rate-limit {:base 10 :mid 15 :max 20}}
  [ids]
  (println "hi"))

(mycall 1 2 3)
{:config-in-call
 {dude/defcall {:linters {:unresolved-symbol {:exclude [opts]},
                          :invalid-arity {:level :off}}}},
 :lint-as {dude/defcall clojure.core/defn}}
$ clj-kondo --lint dude.clj
dude.clj:8:4: warning: unused binding ids
dude.clj:11:1: error: dude/mycall is called with 3 args but expects 1
linting took 7ms, errors: 1, warnings: 1
It's the error that bothers you right? The issue is that mycall isn't a call "in" dude/defcall, but you're calling something produced by defcall. The only good solution here is to use a hook that expands the defcall call to something which actually spits out a defn with a varargs signature.

2026-04-02T11:17:45.757779Z

Yeah, that's it. I was already reading the hooks docs, and it'll probably be good for me to learn it. I was just avoiding it since then I have to set up my library to explicitly export this information so downstream projects can pick it up.

2026-04-02T11:17:52.371529Z

Thanks for your help!

borkdude 2026-04-02T11:19:43.716509Z

the :macroexpand hook is probably the easiest to start with

👍 1
István Karaszi 2026-04-02T15:34:12.163019Z

Potential false-positive on conj!

✅ 1
István Karaszi 2026-04-02T15:34:32.865909Z

Is there anything wrong with this?

(defn x []
  (let [items (transient [])]
    (doseq [item [:a :b :c]]
      (conj! items item))
    items))

István Karaszi 2026-04-02T15:35:07.502719Z

Because I get an Unused value warning on this

lassemaatta 2026-04-02T15:36:48.356969Z

I think you need to grab the return value of conj!

☝️ 1
Alex Miller (Clojure team) 2026-04-02T15:37:10.771789Z

conj! is not "bash in place", it is the same model as conj - use the return

István Karaszi 2026-04-02T15:38:23.581599Z

Then I used it completely wrong for the whole time, but it is working like that

István Karaszi 2026-04-02T15:38:38.851999Z

Is that by just an accident?

Alex Miller (Clojure team) 2026-04-02T15:38:42.483599Z

it will "work" up to 8 items but then it will very much not work

Alex Miller (Clojure team) 2026-04-02T15:39:08.020899Z

or maybe it's 32 items, can't remember the break

István Karaszi 2026-04-02T15:39:18.083619Z

I collected 40 items without issues 🙂

lassemaatta 2026-04-02T15:39:18.568019Z

60% of the time it works every time 🙂

😅 3
István Karaszi 2026-04-02T15:40:33.144249Z

Thank you! TIme to fix a potential bug in the production code as well then

rolt 2026-04-02T16:06:02.816829Z

it's 8 for maps (ArrayMap -> HashMap)

rolt 2026-04-02T16:06:36.628189Z

(defn x [n]
  (let [items (transient {})]
    (doseq [item (range n)]
      (conj! items [item item]))
    (persistent! items)))
(x 9)

István Karaszi 2026-04-02T16:15:45.860209Z

I was using sets, and I could collect 40 items easily with that

Alex Miller (Clojure team) 2026-04-02T16:17:19.601459Z

In any case, you should not

👍 1
2026-04-02T16:22:45.186269Z

it is an extremely common mistake to make with transients which is a real bummer

lassemaatta 2026-04-02T16:27:21.712339Z

it looks like ATransientSet notices if the backing map changes and always just returns this, which might explain why you didn't run into problems with sets. But I'd say this is a happy little accident rather than something you should rely on.

👍 1
István Karaszi 2026-04-02T16:28:03.889529Z

Sure thing, I’ve already fixed the code

Alex Miller (Clojure team) 2026-04-02T16:29:41.336199Z

the docstring for transient was updated in 1.12 to mention this

👍 1
István Karaszi 2026-04-02T16:30:28.567019Z

I can see that, now: > Note in particular that transients are not designed to be bashed in-place. You must capture and use the return value in the next call. In this way, they support the same code structure as the functional persistent code they replace.

István Karaszi 2026-04-02T16:31:47.366059Z

Most of the time I am using the http://clojuredocs.org offline dump, which has not been updated for awhile now

István Karaszi 2026-04-02T16:35:49.017769Z

Anyways, thank you TIL!