clojure-lsp / clojure-lsp

Clojure & ClojureScript Language Server (LSP) implementation

Home Page:https://clojure-lsp.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow reusing of another functions `:doc` and `:arglists`

PEZ opened this issue · comments

Is your feature request related to a problem? Please describe.

function-a takes the same arguments as function-b. In fact, function-a calls function-b. Sometimes they also share docs. E.g. with Polylith style interfaces and implementations. Without too much synchronized updating of the function signatures, I'd like for clojure-lsp to show me the same docs/hover info for the two functions.

At the REPL, this works:

(defn function-b
  "Prints `:a`, `:b`, and `:c` from the argment map.
   * :a The a of the map
   * :b The b of the map
   * :c The c of the map"
  [{:keys [a b c]}]
  (println a b c))

(defn function-a
  {:arglists (:arglists (meta #'function-b))
   :doc (:doc (meta #'function-b))}
  [args]
  (function-b args))

It makes the sharing very clear.

(doc function-b) will print:

-------------------------
.../function-b
([{:keys [a b c]}])
  Prints `:a`, `:b`, and `:c` from the argment map.
   * :a The a of the map
   * :b The b of the map
   * :c The c of the map

Describe the solution you'd like

It would be super nice if it worked with clojure-lsp too, making for a shared way for the dynamic and static tools to help with this.

Describe alternatives you've considered

There are some ways to make the sharing a bit less verbose than full copies of the the docs and argument lists, but I don't think none is this clear and complete and also at the same time works for the REPL.

Additional context

Here's a write-up about the general problem and some ways to deal with it with the current clojure-lsp: https://blog.agical.se/en/posts/keeping-the--arglists-of-clojure-functions-dry/

Here's a Clojurians Slack thread where that blog post started from: https://clojurians.slack.com/archives/C03S1KBA2/p1713449139604419 (It also reveals that the actual suggestion in this feature request comes from @ericdallo 😄 )

Note that function-a as defined will fail to compile when its body contains a call to itself:
https://ask.clojure.org/index.php/13834/compiler-exception-dynamically-arglists-metadata-recursive

The interpretation given by @puredanger in the linked thread was that :arglists in the attr-map should be restricted to literal quoted lists, so expressions to be evaluated like (:arglists (meta #'function-b)) should be at least discouraged (if not considered illegal).

To avoid this construction, one has to use alter-meta! on the var after its creation, which I imagine is trickier to statically analyze:

(defn function-a [args] 
  (function-b args)

(alter-meta! #'function-a 
             assoc  
             :arglists (:arglists (meta #'function-b)))
;; or
(alter-meta! #'function-a 
             merge 
             (select-keys (meta #'function-b)
                          [:arglists :doc]))