clj-kondo 2025-04-17

Hi! Thanks again for clj-kondo, I am spelunking an enormous codebase and this tool is proving to be very valuable. 🙇 The code I'm investigating makes use of deftype to implement Java interfaces, and the analysis output doesn't seem to include enough information for me to reconstruct the whole call graph in the way I'd like (perhaps I'm doing something wrong). Here's small example. Given a namespace like:

(ns core)

(deftype SillyIter []
  java.util.Iterator
  (hasNext [_this] true)
  (next [_this] 42))

(defn a
  []
  (let [s (SillyIter.)]
    (when (.hasNext s)
      [(.next s) (.next s) (.next s)])))

(defn b
  []
  (a))

(defn c
  []
  (a))

(a)
(b)
(c)
--- I call clj-kondo --config '{:analysis {} :output {:format :edn}}' --lint . > kondo-output.edn And the output looks like this:
core> (->> (clojure.edn/read-string (slurp "kondo-output.edn"))
           (:analysis)
           (:var-usages)
           (clojure.pprint/print-table [:name :macro :from :row :to]))

|     :name | :macro | :from | :row |          :to |
|-----------+--------+-------+------+--------------|
|   deftype |   true |  core |    3 | clojure.core |
| SillyIter |        |  core |   10 |         core |
|         . |        |  core |   11 | clojure.core |
|         . |        |  core |   12 | clojure.core |
|         . |        |  core |   12 | clojure.core |
|         . |        |  core |   12 | clojure.core |
|      when |   true |  core |   11 | clojure.core |
|       let |   true |  core |   10 | clojure.core |
|      defn |   true |  core |    8 | clojure.core |
|         a |        |  core |   16 |         core |
|      defn |   true |  core |   14 | clojure.core |
|         a |        |  core |   20 |         core |
|      defn |   true |  core |   18 | clojure.core |
|         a |        |  core |   22 |         core |
|         b |        |  core |   23 |         core |
|         c |        |  core |   24 |         core | 
--- What I'd love to discover programmatically is that b and c call through to the SillyIter implementation of hasNext and next . The bit that is missing is the calls to hasNext and next - presumably these are the . special form things in the table? Is there a different "config" option I can pass? Or maybe some possible improvement to the analysis to better handle this case? What do you think? Thanks!

➕ 1

Currently we use for https://clojure-lsp.io/features/#semantic-tokens only, but I believe this is closer to have things like, go to definition and go to the java class or so

Got it, that's super 🆒 cool. Associating it with the java class would be great, from what I see in the data currently there is no type information. Our codebase (perhaps unsurprisingly) has more than one implementation of next and hasNext, knowing which interface and which concrete type would be neat. It also seems that currently the best way to infer the caller is by the :name-row, which strikes me as perhaps a little to 'implicit', though I'd have to go further into trying to build the graph - for example, Morpheus (mentioned and linked above) uses the :`from` and :to keys to build the graph of var usages. Perhaps having something similar here would also be helpful. You two are amazing, and I really appreciate your thoughts.

thanks! Yeah, agreed, if we had the concrete info about the instance we would manage to have multiple features on clojure-lsp like completion, find definition, references etc, it's something I'd love to see indeed, but it's a little bit complex I think as we need to track during analysis the instance (if we really can infer that safely)

👍 1
🙂 1

clj-kondo already has some kind of tracking, but not about java types

more about number/string/map/vector etc

there is actually the :instance-invocations arg in :analysis that add info about the .hasNext and next but it's limited, so not sure it it's enough for you

oh thanks for adding that :)

👍 1

oh, I saw that and tried it, but I must have flubbed it. I tried it again on the small example above and it did add some data to the end file:

:instance-invocations
  [{:method-name "hasNext",
    :filename "./src/core.clj",
    :name-row 11,
    :name-col 12,
    :name-end-row 11,
    :name-end-col 20}
   {:method-name "next",
    :filename "./src/core.clj",
    :name-row 12,
    :name-col 9,
    :name-end-row 12,
    :name-end-col 14}
   {:method-name "next",
    :filename "./src/core.clj",
    :name-row 12,
    :name-col 19,
    :name-end-row 12,
    :name-end-col 24}
   {:method-name "next",
    :filename "./src/core.clj",
    :name-row 12,
    :name-col 29,
    :name-end-row 12,
    :name-end-col 34}]
That's certainly better than nothing, @ericdallo - do you use that data to reconstruct more complete call graphs?

I think it should be possible somehow since lsp implements call-graph features on top of this and also this tool does it: https://github.com/benedekfazekas/morpheus Perhaps @ericdallo can tell you more. Have to run now, back later

Cool, thanks!

Morpheus is rad - hadn't seen that, thank you. Here is its output for the b function:

This agrees with what I'm seeing in the raw data (no mention of a's usage of hasNext or next).

that's right, instance methods aren't included in the analysis yet

Thanks for confirming that, very helpful. I'm still a bit of a n00b here. I really appreciate your effort with this lib (and everything else!). I will ponder this further.

we could add this information, but so far it hasn't been that useful since clj-kondo doesn't know the type of many interop-related things on which method calls are called

That makes sense. In this simple example, the types are not that important (it's clear sort of just by looking at it what's going on)... In the bigger codebase there could be additional wrinkles as I believe there are multiple (different) interfaces with same-named methods in them. The call sites (even in the simple example) expand to (. s next) , which is a bit impoverished. But perhaps there's enough context in the analyzer at that point to know that s is a SillyIter ?

right now there probably isn't since it was never important enough so far

I see. Let me ponder this, and if it's important to us we can contribute some effort. Thanks!