#?(:clj
(do (defn ^:no-doc arity-map [f]
(let [^"[Ljava.lang.reflect.Method;" methods (.getDeclaredMethods (class f))
pairs (for [^Method m methods
:let [name (.getName m)]
:when (not (#{"withMeta" "meta" "invokeStatic"} name))]
(condp = name
"invoke" [:invoke (alength (.getParameterTypes m))]
"doInvoke" [:doInvoke true]
"getRequiredArity" [:getRequiredArity
(.getRequiredArity ^RestFn f)]))
facts (group-by first pairs)]
{:arities (into #{} (map peek) (:invoke facts))
:required-arity (second (first (:getRequiredArity facts)))
:invoke? (boolean (seq (:doInvoke facts)))}))
(defn ^:no-doc jvm-arity [f]
(let [{:keys [arities required-arity invoke?] :as m} (arity-map f)]
(cond
(and (= 1 (count arities))
(not required-arity)
(not invoke?))
[:exactly (first arities)]
(and (= #{0 1 2 3} arities)
(= 3 required-arity)
invoke?)
[:exactly 1]
(and required-arity
invoke?)
[:at-least (apply min required-arity arities)]
(seq arities)
[:between
(apply min arities)
(apply max arities)]
:else
(u/illegal
(str "Not enough info to determine jvm-arity of " f " :" m))))))
:cljs
(do
(defn ^:no-doc variadic?
"Returns true if the supplied function is variadic, false otherwise."
[f]
(boolean
(.-cljs$core$IFn$_invoke$arity$variadic f)))
(defn ^:no-doc exposed-arities
"When CLJS functions have different arities, the function is represented as a js
object with each arity storied under its own key."
[f]
(let [pattern (re-pattern #"invoke\$arity\$\d+")
parse (fn [s]
(when-let [arity (re-find pattern s)]
(js/parseInt (subs arity 13))))
arities (->> (map parse (js-keys f))
(concat [(.-cljs$lang$maxFixedArity f)])
(remove nil?)
(into #{}))]
(if (empty? arities)
[(alength f)]
(sort arities))))
(defn ^:no-doc js-arity
"Returns a data structure indicating the arity of the supplied function."
[f]
(let [arities (exposed-arities f)]
(cond (variadic? f)
(if (= [0 1 2 3] arities)
[:exactly 1]
[:at-least (first arities)])
(= 1 (count arities)) [:exactly (first arities)]
:else [:between
(first arities)
(last arities)])))))