This. With the strong interop story that Clojure has, looking up docs and signatures, while using it, is super convenient.
As for the whole initiative. Love it! Let me throw in some inspiration:
This. With the strong interop story that Clojure has, looking up docs and signatures, while using it, is super convenient.
As for the whole initiative. Love it! Let me throw in some inspiration:
I’ll see your inspiration, and I’ll raise you one. Bozhidar says:
There are two potential classes that implement the
toUpperCase
method. One isjava.lang.String
and the other isjava.lang.Character
. We cannot possibly know which one you are trying to evaluate here. … It’s not ideal, but it simply cannot be done in any other way.
Ummm… in the example, he’s literally calling it with a String receiver. Here’s Cursive:
I hear you say: but that’s a trivial example, and real code doesn’t look like that! Ok:
Cursive runs local type inference in the editor, mimicking what the Clojure compiler does. This allows you to have almost Java-level completions. The main issue is that in Clojure, when you’re typing out your code, the method comes before the receiver. No problem, you can do this:
Put the receiver first and use completion for your interop, and since Cursive knows the type of the receiver, you’ll only get completions relevant for that type. When you actually complete the method, Cursive will swap them around for you:
You don’t need to do that if your code is naturally structured in a way which allows the type to be determined:
Since the receiver comes first in the threading form, Cursive knows its type with no swapping required.
Additionally, note that neither List nor Iterator are imported in this ns, but Cursive knows their types due to the inference. If you want to know the inferred type at any time, you can just ask:
There’s plenty more, I could go on… this all works for Kotlin and Scala code too, you can rename Java/Kotlin/Scala methods from your Clojure code, finding usages of the JVM methods will find the usages in your Clojure code and vice versa, etc etc.
There’s lots that can be done in this space!
Thanks for sharing Colin, that’s really cool. Clj-kondo has a basic form of type inference too that could be leveraged here. Still contemplating which route to go for Java bytecode/source analysis.
I also think just listing out the completion of all possible types grouped by each type would be great.
Like I’m smart enough to quickly find the section of the type I know I have. Bonus point if it’s ordered by child-most type.
One question, what it it came from a global or a function parameter that had a type hint, would completion also work then?
Yes, it does. The subs
example above knows about the type because subs
has a type hint saying what it will return. Local function arg hints and local binding hints also work, as well as various places where things are implicitly type hinted (e.g. this
args in reify/deftype/extend-type method implementations).
Ok, maybe I’m pushing my luck, but how far does this the inference goes?
Example 1:
(defn hello
[]
"hello"
(.toUpperCase (hello))
No type hint on hello
, but local inference of hello should know the type from the literal and infer the return type of hello. Is that then able to be used external to the function by the call to .toUpperCase
on (hello)
Example 2:
(defn hello
[name]
(str "hello " name))
(.toUpperCase (hello))
Would clojure.core functions, even though not type hinted, have their common types be known by Cursive magically (probably hard-coded somewhere)
Example 3:
(defn make-info
[^String name]
{:name name})
(.toUpperCase (:name (make-info)))
This one might be a tougher one, but basically can it infer the type of values on maps? Like would it know this is a Variant [^Keyword :name ^String name] and then know that the Keyword fn :name
returns the value of key?
No, this inference currently only does what Clojure itself does. I could potentially do more, but it would only be in order to suggest to the user where they might want to add further type hints. Cases like the function return types are relatively easy to implement if I decide to go that far. The map value one is probably going beyond what would be worth it, though.
Would clojure.core functions, even though not type hinted, have their common types be known by Cursive magically (probably hard-coded somewhere)
Currently I’ve avoided magic hard-coding, but most core functions (e.g. str
) are properly type hinted for interop purposes anyway.
So awesome!
Just to clarify what I meant - in CIDER’s case, as we’re 100% REPL-powered (no static analysis at all), we can’t know the type of the receiver unless it’s a literal or we have evaluated it. As type method hints come solely from resolving the method names without any context (we don’t send the whole expressions to the backend, just the method symbol) we’re forced to do some guesswork. We may consider adding some context down the road, at least for the trivial cases, but evaluating receivers is dangerous and potentially slow, so that’s definitely one limitation of our approach, at least for the Java interop. Clearly that’s not an issue for Clojure code, but on the Java front Cursive’s approach is way superior, that’s undeniable.
This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.