Skip to content

Commit 1bbbac7

Browse files
authored
Merge pull request #1176 from frenchy64/issue-859-primitive-instrument
Skip instrumenting primitive fns
2 parents 3c02f9c + 05a5d81 commit 1bbbac7

File tree

4 files changed

+63
-20
lines changed

4 files changed

+63
-20
lines changed

docs/function-schemas.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ Turning instrumentation on:
509509
; =throws=> :malli.core/invalid-output {:output [:int {:max 6}], :value 11, :args [10], :schema [:=> [:cat :int] [:int {:max 6}]]}
510510
```
511511

512+
Note that vars already containing a primitive JVM function will not be instrumented.
513+
512514
#### Function Schema Metadata
513515

514516
`defn` schemas can be defined with standard Var metadata. It allows `defn` schema documentation and instrumentation without dependencies to malli itself from the functions. It's just data.

src/malli/instrument.clj

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,37 @@
88
(defn -f->original [f] (-> f meta ::original (or f)))
99
(defn -original [v] (let [f (deref v)] (-f->original f)))
1010

11-
(defn -filter-ns [& ns] (fn [n _ _] ((set ns) n)))
11+
(defn -filter-ns [& ns] (let [ns (set ns)] (fn [n _ _] (contains? ns n))))
1212
(defn -filter-var [f] (fn [n s _] (f (-find-var n s))))
1313
(defn -filter-schema [f] (fn [_ _ {:keys [schema]}] (f schema)))
1414

15+
(defn- -primitive-fn? [f]
16+
(and (fn? f) (boolean (some (fn [^Class c] (.startsWith (.getName c) "clojure.lang.IFn$")) (supers (class f))))))
17+
1518
(defn -strument!
1619
([] (-strument! nil))
1720
([{:keys [mode data filters gen report] :or {mode :instrument, data (m/function-schemas)} :as options}]
1821
(doall
1922
(for [[n d] data, [s d] d]
20-
(when (or (not filters) (some #(% n s d) filters))
21-
(when-let [v (-find-var n s)]
22-
(case mode
23-
:instrument (let [dgen (as-> (merge (select-keys options [:scope :report :gen]) d) $
24-
(cond-> $ report (update :report (fn [r] (fn [t data] (r t (assoc data :fn-name (symbol (name n) (name s))))))))
25-
(cond (and gen (true? (:gen d))) (assoc $ :gen gen)
26-
(true? (:gen d)) (dissoc $ :gen)
27-
:else $))]
28-
(alter-var-root v (fn [f]
29-
(let [f (-f->original f)]
30-
(-> (m/-instrument dgen f) (with-meta {::original f}))))))
31-
:unstrument (alter-var-root v -f->original)
32-
(mode v d))
33-
v))))))
23+
(when-let [v (-find-var n s)]
24+
(when (and (bound? v)
25+
(or (not (-primitive-fn? @v))
26+
(println (str "WARNING: Not instrumenting primitive fn " v))))
27+
(when (or (not filters) (some #(% n s d) filters))
28+
(case mode
29+
:instrument (let [dgen (as-> (merge (select-keys options [:scope :report :gen]) d) $
30+
(cond-> $ report (update :report (fn [r] (fn [t data] (r t (assoc data :fn-name (symbol (name n) (name s))))))))
31+
(cond (and gen (true? (:gen d))) (assoc $ :gen gen)
32+
(true? (:gen d)) (dissoc $ :gen)
33+
:else $))]
34+
(alter-var-root v (fn [f]
35+
(when (-primitive-fn? f)
36+
(m/-fail! ::cannot-instrument-primitive-fn {:v v}))
37+
(let [f (-f->original f)]
38+
(-> (m/-instrument dgen f) (with-meta {::original f}))))))
39+
:unstrument (alter-var-root v -f->original)
40+
(mode v d))
41+
v)))))))
3442

3543
(defn -schema [v]
3644
(let [{:keys [malli/schema arglists]} (meta v)]

src/malli/instrument.cljs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@
9797
([{:keys [mode data filters gen report skip-instrumented?] :or {skip-instrumented? false
9898
mode :instrument, data (m/function-schemas :cljs)} :as options}]
9999
(doseq [[n d] data, [s d] d]
100-
(when (or (not filters) (some #(% n s d) filters))
101-
(when-let [v (-find-var n s)]
100+
(when-let [v (-find-var n s)]
101+
(when (or (not filters) (some #(% n s d) filters))
102102
(case mode
103103
:instrument (let [original-fn (or (-original v) v)
104104
dgen (as-> (select-keys options [:scope :report :gen]) $

test/malli/instrument_test.clj

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
(ns malli.instrument-test
2-
(:require [clojure.test :refer [deftest is testing]]
2+
(:require [clojure.string :as str]
3+
[clojure.test :refer [deftest is testing]]
34
[malli.core :as m]
45
[malli.instrument :as mi]))
56

@@ -8,8 +9,14 @@
89

910
(defn ->plus [] plus)
1011

11-
(defn unstrument! [] (with-out-str (mi/unstrument! {:filters [(mi/-filter-ns 'malli.instrument-test)]})))
12-
(defn instrument! [] (with-out-str (mi/instrument! {:filters [(mi/-filter-ns 'malli.instrument-test)]})))
12+
(defn primitive-DO [^double val] val)
13+
(m/=> primitive-DO [:=> [:cat :double] :double])
14+
15+
(defn opts [] {:filters [(fn [& args]
16+
(and (apply (mi/-filter-ns 'malli.instrument-test) args)
17+
(apply (mi/-filter-var #(not= #'primitive-DO %)) args)))]})
18+
(defn unstrument! [] (with-out-str (mi/unstrument! (opts))))
19+
(defn instrument! [] (with-out-str (mi/instrument! (opts))))
1320

1421
(deftest instrument!-test
1522

@@ -31,6 +38,32 @@
3138
#":malli.core/invalid-output"
3239
((->plus) 6)))))
3340

41+
(defmacro if-bb [then & [else]]
42+
(if (System/getProperty "babashka.version")
43+
then
44+
else))
45+
46+
(if-bb
47+
(deftest primitive-functions-can-be-instrumented
48+
(is (= 42 (primitive-DO 42)))
49+
(is (= "42" (primitive-DO "42")))
50+
(is (mi/instrument! {:filters [(mi/-filter-var #(= #'primitive-DO %))]}))
51+
(is (thrown-with-msg?
52+
Exception
53+
#":malli.core/invalid-input"
54+
(primitive-DO "42")))
55+
(is (mi/unstrument! {:filters [(mi/-filter-var #(= #'primitive-DO %))]}))
56+
(is (= 42 (primitive-DO 42)))
57+
(is (= "42" (primitive-DO "42"))))
58+
(deftest primitive-functions-cannot-be-instrumented
59+
(is (= 42.0 (primitive-DO 42)))
60+
(is (thrown? ClassCastException (primitive-DO "42")))
61+
(is (str/includes?
62+
(with-out-str (mi/instrument! {:filters [(mi/-filter-var #(= #'primitive-DO %))]}))
63+
"WARNING: Not instrumenting primitive fn #'malli.instrument-test/primitive-DO"))
64+
(is (= 42.0 (primitive-DO 42)))
65+
(is (thrown? ClassCastException (primitive-DO "42")))))
66+
3467
(defn minus
3568
"kukka"
3669
{:malli/schema [:=> [:cat :int] [:int {:min 6}]]

0 commit comments

Comments
 (0)