(ns metabase.util.malli.defn (:refer-clojure :exclude [defn defn-]) (:require [clojure.core :as core] [clojure.string :as str] [malli.destructure] [metabase.util :as u] [metabase.util.malli.fn :as mu.fn] [net.cgrand.macrovich :as macros])) | |
(set! *warn-on-reflection* true) | |
TODO -- this should generate type hints from the schemas and from the return type as well. | (core/defn- deparameterized-arglist [{:keys [args]}]
(-> (malli.destructure/parse args)
:arglist
(with-meta (macros/case
:cljs
(meta args)
;; make sure we resolve classnames e.g. `java.sql.Connection` intstead of `Connection`, otherwise the
;; tags won't work if you use them in another namespace that doesn't import that class. (Clj only)
:clj
(let [args-meta (meta args)
tag (:tag args-meta)
resolved-tag (when (symbol? tag)
(let [resolved (ns-resolve *ns* tag)]
(when (class? resolved)
(symbol (.getName ^Class resolved)))))]
(cond-> args-meta
resolved-tag (assoc :tag resolved-tag))))))) |
(core/defn- deparameterized-arglists [{:keys [arities], :as _parsed}]
(let [[arities-type arities-value] arities]
(case arities-type
:single (list (deparameterized-arglist arities-value))
:multiple (map deparameterized-arglist (:arities arities-value))))) | |
Generate a docstring with additional information about inputs and return type using a parsed fn tail (as parsed by [[mx/SchematizedParams]]). | (core/defn- annotated-docstring
[{original-docstring :doc
[arities-type arities-value] :arities
:keys [return]
:as _parsed}]
(str/trim
(str "Inputs: " (case arities-type
:single (pr-str (:args arities-value))
:multiple (str "("
(str/join "\n "
(map (comp pr-str :args)
(:arities arities-value)))
")"))
"\n Return: " (str/replace (u/pprint-to-str (:schema return :any))
"\n"
"\n ")
(when (not-empty original-docstring)
(str "\n\n " original-docstring))))) |
Implementation of [[metabase.util.malli/defn]]. Like [[schema.core/defn]], but for Malli. Doesn't Malli already have a version of this in [[malli.experimental]]? It does, but it tends to eat memory; see https://metaboat.slack.com/archives/CKZEMT1MJ/p1690496060299339 and #32843 for more information. This new implementation solves most of our memory consumption problems. Unless it's in a skipped namespace during prod, (see: [[mu.fn/instrument-ns?]]) this macro emits clojure code to validate its inputs and outputs based on its malli schema annotations. Example macroexpansion: (mu/defn f :- :int [x :- :int] (inc x)) ;; => (def f (let [&f (fn [x] (inc x))] (fn ([a] (metabase.util.malli.fn/validate-input :int a) (->> (&f a) (metabase.util.malli.fn/validate-output :int)))))) Known issue: does not currently generate automatic type hints the way [[schema.core/defn]] does, nor does it attempt to preserve them if you specify them manually. We can fix this in the future. | (defmacro defn
[& [fn-name :as fn-tail]]
(let [parsed (mu.fn/parse-fn-tail fn-tail)
cosmetic-name (gensym (munge (str fn-name)))
{attr-map :meta} parsed
attr-map (merge
{:arglists (list 'quote (deparameterized-arglists parsed))
:schema (mu.fn/fn-schema parsed {:target :target/metadata})}
attr-map)
docstring (annotated-docstring parsed)
instrument? (mu.fn/instrument-ns? *ns*)]
(if-not instrument?
`(def ~(vary-meta fn-name merge attr-map)
~docstring
~(mu.fn/deparameterized-fn-form parsed cosmetic-name))
`(def ~(vary-meta fn-name merge attr-map)
~docstring
~(macros/case
:clj (let [error-context {:fn-name (list 'quote fn-name)}]
(mu.fn/instrumented-fn-form error-context parsed cosmetic-name))
:cljs (mu.fn/deparameterized-fn-form parsed cosmetic-name)))))) |
Same as defn, but creates a private def. | (defmacro defn-
[fn-name & fn-tail]
`(defn
~(with-meta fn-name (assoc (meta fn-name) :private true))
~@fn-tail)) |