Hey, I came upon a strange issue with better-cond.core/when-let-hook. Claude helped me fix it, I'll put a summary in the thread. Seeing it was @borkdude who wrote the hook, I wanted to double-check the fix here rather than opening a PR.
Setup. foo/+when-let is a var alias of better-cond.core/when-let (`def ^:macro` + @#'), linted by pointing :analyze-call at better-cond's own hooks.
(def ^{:macro true :arglists (:arglists (meta #'bcond/when-let))}
+when-let @#'bcond/when-let)
Issue. +when-let / +when-some reported binding symbols as unresolved unless the calling namespace also required better-cond.core. +if-let / +if-some were fine.
Cause. when-let-hook doesn't expand to core forms — it emits a better-cond.core/if-let node for re-analysis. That symbol has to resolve in the linted namespace for if-let-hook to fire on it; without the require it doesn't, so the binding vector is analyzed as a plain vector. if-let-hook expands straight to core forms, so it never had the problem.
Fix. Finish the expansion in your own hook, since {:node ...} out of one hook is valid input to the next:
;; .clj-kondo/hooks/foo.clj
(ns hooks.foo
(:require [better-cond.core :as bc]))
(defn +when-let [ctx]
(-> ctx bc/when-let-hook bc/if-let-hook))
(def +when-some +when-let)
;; .clj-kondo/config.edn
{:hooks {:analyze-call {foo/+when-let hooks.foo/+when-let
foo/+when-some hooks.foo/+when-some
foo/+if-let better-cond.core/if-let-hook
foo/+if-some better-cond.core/if-let-hook
foo/+cond better-cond.core/cond-hook}}}I have little time today. Can you boil down the issue into one human-readable sentence for me perhaps? Else I'll have to look at this later this week :)
It's not urgent at all 🙂 That said, here's an attempt: better-cond's when-let-hook only half-expands — it emits a better-cond.core/if-let node that won't resolve unless the calling namespace requires better-cond.
And is that a bug? It's what the original is doing right?
It is what the original is doing, but I get the unresolved symbols linting error with my setup above. Sorry the summary is a bit terse, but I think it captures everything. Take a look when you have some time, no rush.
The solution for you could be to add better-cond.core to unresolved namespace excluded since your macro just assumes that it will be already loaded
In your fix prose I don't understand who is meant with “your”
Did you write this prose?
No, Claude did. "Your" is me.
So the fix is to fix your own hook? Then why did you ask me?
No, Claude suggested I add my own hook that fixes the hook from the better-cond repo.
Maybe you can make a GitHub repro of your problem, then I'll run it locally
Here you go: https://github.com/zeitstein/better-cond-clj-kondo-repro
Can you tell me the command line instructions to repro your issue?
Running clj-kondo --lint src in the repo I see:
src/bar.clj:9:3: warning: Unresolved namespace better-cond.core. Are you missing a require?
src/bar.clj:9:19: error: Unresolved symbol: x
src/bar.clj:9:26: error: Unresolved symbol: y
src/bar.clj:12:17: warning: unused binding x
src/bar.clj:12:24: warning: unused binding y
src/bar.clj:13:18: warning: unused binding x
src/bar.clj:13:25: warning: unused binding y
src/foo.clj:17:15: warning: unused binding x
src/foo.clj:17:22: warning: unused binding y
I didn't see the unresolved namespace one in my IDE. I guess that is what you meant.I'm pushing a new commit with the suggested fix.
Now I get:
filip@mac better-cond-clj-kondo-repro % clj-kondo --lint src
src/bar.clj:9:19: warning: unused binding x
src/bar.clj:9:26: warning: unused binding y
src/bar.clj:10:3: warning: Unresolved namespace better-cond.core. Are you missing a require?
src/bar.clj:10:20: error: Unresolved symbol: x
src/bar.clj:10:27: error: Unresolved symbol: y
src/bar.clj:12:17: warning: unused binding x
src/bar.clj:12:24: warning: unused binding y
src/bar.clj:13:18: warning: unused binding x
src/bar.clj:13:25: warning: unused binding y
src/foo.clj:17:15: warning: unused binding x
src/foo.clj:17:22: warning: unused binding y
linting took 70ms, errors: 2, warnings: 9$ clj-kondo --lint src
WARNING: file better_cond/core not found while loading hook
WARNING: error while trying to read hook for foo/+when-let: Could not find namespace: better-cond.core.
WARNING: file better_cond/core not found while loading hook
WARNING: error while trying to read hook for foo/+when-some: Could not find namespace: better-cond.core.
WARNING: file better_cond/core not found while loading hook
WARNING: error while trying to read hook for foo/+if-let: Could not find namespace: better-cond.core.
WARNING: file better_cond/core not found while loading hook
WARNING: error while trying to read hook for foo/+if-some: Could not find namespace: better-cond.core.
src/bar.clj:9:19: error: Unresolved symbol: x
src/bar.clj:9:26: error: Unresolved symbol: y
src/foo.clj:17:15: error: Unresolved symbol: x
src/foo.clj:17:22: error: Unresolved symbol: y
linting took 82ms, errors: 4, warnings: 0where are your better cond hook coming from? can you make the repro less work for me?
They were auto-added (by Calva? not sure). I'll commit them too.
Done. Pushed.
normally that happens when you do this:
clj-kondo --lint "$(clojure -Spath)" --dependencies --copy-configsbut I don't see that happening here
$ clj-kondo --lint "$(clojure -Spath)" --dependencies --copy-configs
Configs copied:
- .clj-kondo/imports/better-cond/better-condso what I meant was:
{:hooks {:analyze-call {foo/+when-let better-cond.core/when-let-hook
foo/+when-some better-cond.core/when-let-hook
foo/+if-let better-cond.core/if-let-hook
foo/+if-some better-cond.core/if-let-hook}}
:linters {:unresolved-namespace {:exclude [better-cond.core]}}}then you are left with only unused bindings, which may or may not be correct for your use case?
I think that's the correct behavior
It is correct. Also matches the behaviour with the 'fixed' hook.
What I don't get is why this is happening 🙂
I mean, linting for +if-let works without 'hacks'.
what the last config line does: it makes clj-kondo think that better-cond.core is already required somewhere else, which is what you want, since you require it in your foo namespace so if the macro returns better-cond.core/when, then that's ok, even if you haven't required it in another namespace
if-let probably works since that doesn't have references to better-cond itself anymore?
Sure, I get that. But why would I need to require the namespace (or do your 'hack') in the first place. I don't need that for +if-let. So why for +when-let? Is it pointing to a genuine issue or (probably) I'm missing something about the whole thing 🙂
can you give a permalink to if-let and when-let hooks?
https://github.com/zeitstein/better-cond-clj-kondo-repro/blob/a3a51bde6f7dd0038a132470626b21b502552a2a/.clj-kondo/imports/better-cond/better-cond/better_cond/core.clj#L113-L131 (that was auto-imported from better-cond repo, no changes)
well, do you see any references to better cond in the expansion of if-let? and when-let? what is the difference? and what do those differences mean?
I don't know in the context of clj-kondo, I was hoping you'd tell me 🙂
it works just like regular macros. the expansion if when let is something like (better-cond.core/if-let [x ..] then nil)
so when clj-kondo sees that in a namespace that doesn't explicitly require better-cond.core, you'll get a warning about that. the :exclude option prevents that and tells clj-kondo that the namespace can be considered "already loaded".
So you don't think this points to an issue with the with-let-hook itself?
well, it's not a bug, it's mostly a convenience issue
perhaps I can change clj-kondo to regard namespaces in expansions to be loaded or something... that would maybe also hide some real errors, not sure which is best
you can change the hook upstream if you would like, if they are open to it, I don't maintain it
> perhaps I can change clj-kondo to regard namespaces in expansions to be loaded or something... that would maybe also hide some real errors, not sure which is best Yeah, I don't think that's a good idea.
What I learned from this is that maybe macro hooks should expand until they bottom out at clojure.core? Anyway, I see how the current hooks work for regular consumers of the lib. I'll 'fix' this for myself.
Thanks for your time!
you can also fix it by adding a [better-cond.core] at the top of your namespace in calling namespaces, but I can see that is a bit tedious as well
I don't 100% understand what you mean here:
> better-cond.core to unresolved namespace excluded
How would that help?
This is the linting error. (Sorry renamed foo/+when-let to u/when-lets)