diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn new file mode 100644 index 0000000..9e8f6f0 --- /dev/null +++ b/.clj-kondo/config.edn @@ -0,0 +1 @@ +{:config-paths ["../resources/clj-kondo.exports/cnuernber/ham-fisted"]} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 54f1fe4..cd1052c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,8 @@ jobs: uses: DeLaGuardo/setup-clojure@12.1 with: cli: 1.11.1.1413 + - name: Run clojure linter + run: scripts/lint - name: Run automated tests run: scripts/run-tests - name: Cache dependencies diff --git a/.gitignore b/.gitignore index d35bba1..938382d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ pom.xml *.asc issue-data jdk-* +.clj-kondo +.lsp diff --git a/CHANGELOG.md b/CHANGELOG.md index c9bf42a..0497ee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.XXX + * Add clj-kondo exports and config, fix linting errors + * Remove support for and call to `take-last` 1-arity, which was not valid. + * Fix variable arity `merge-with`, which was not correctly implemented. + # 2.017 * Faster compose-reducers especially where there really are a lot of reducers. diff --git a/deps.edn b/deps.edn index 6b69cda..e332730 100644 --- a/deps.edn +++ b/deps.edn @@ -1,4 +1,4 @@ -{:paths ["src" "target/classes"] +{:paths ["src" "resources" "target/classes"] :deps {it.unimi.dsi/fastutil-core {:mvn/version "8.5.8"} com.github.ben-manes.caffeine/caffeine {:mvn/version "2.9.3"}} @@ -22,6 +22,8 @@ :build {:deps {io.github.clojure/tools.build {:git/tag "v0.9.6" :git/sha "8e78bcc"}} :ns-default build} + :clj-kondo {:extra-deps {clj-kondo/clj-kondo {:mvn/version "2024.02.12"}} + :main-opts ["-m" "clj-kondo.main"]} :test {:extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner" diff --git a/dev/src/perftest.clj b/dev/src/perftest.clj index c2c940c..5d9065b 100644 --- a/dev/src/perftest.clj +++ b/dev/src/perftest.clj @@ -46,7 +46,7 @@ (def map-non-numeric-constructors {:clj #(into {} %) - :hamf-trie hamf/mut-trie-map + #_#_:hamf-trie hamf/mut-trie-map :hamf-hashmap hamf/mut-map :java hamf/java-hashmap}) @@ -80,7 +80,7 @@ [(merge (hamf/mapmap (fn [entry] [(key entry) (benchmark-us ((val entry) data))]) map-constructors) - {:n-elems n-elems :test :hashmap-construction :numeric? numeric?} ) + {:n-elems n-elems :test :hashmap-construction :numeric? numeric?}) (merge (hamf/mapmap #(vector (key %) (benchmark-us (reduce (fn [acc data] @@ -92,7 +92,7 @@ (merge (hamf/mapmap #(vector (key %) (benchmark-us (fn [acc map] - (reduce (fn [kv] kv) + (reduce (fn [& kv] kv) nil (val %))))) map-data) diff --git a/resources/clj-kondo.exports/cnuernber/ham-fisted/config.edn b/resources/clj-kondo.exports/cnuernber/ham-fisted/config.edn new file mode 100644 index 0000000..e6ec2bc --- /dev/null +++ b/resources/clj-kondo.exports/cnuernber/ham-fisted/config.edn @@ -0,0 +1,21 @@ +{:skip-comments true + :lint-as {ham-fisted.defprotocol/defprotocol clojure.core/defprotocol + ham-fisted.defprotocol/extend-protocol clojure.core/extend-protocol + ham-fisted.defprotocol/extend-type clojure.core/extend-type} + :hooks {:analyze-call {ham-fisted.hlet/let hooks.ham-fisted/analyze-hlet-macro + ham-fisted.function/function hooks.ham-fisted/analyze-1-arg-fn-macro + ham-fisted.function/long-predicate hooks.ham-fisted/analyze-1-arg-fn-macro + ham-fisted.function/long-unary-operator hooks.ham-fisted/analyze-1-arg-fn-macro + ham-fisted.function/double-predicate hooks.ham-fisted/analyze-1-arg-fn-macro + ham-fisted.function/double-unary-operator hooks.ham-fisted/analyze-1-arg-fn-macro + ham-fisted.function/obj->long hooks.ham-fisted/analyze-1-arg-fn-macro + ham-fisted.function/long->double hooks.ham-fisted/analyze-1-arg-fn-macro + ham-fisted.function/bi-function hooks.ham-fisted/analyze-2-arg-fn-macro + ham-fisted.function/binary-predicate hooks.ham-fisted/analyze-2-arg-fn-macro + ham-fisted.function/long-binary-operator hooks.ham-fisted/analyze-2-arg-fn-macro + ham-fisted.function/double-binary-operator hooks.ham-fisted/analyze-2-arg-fn-macro + ham-fisted.reduce/long-accumulator hooks.ham-fisted/analyze-2-arg-fn-macro + ham-fisted.reduce/double-accumulator hooks.ham-fisted/analyze-2-arg-fn-macro + ham-fisted.reduce/indexed-accum hooks.ham-fisted/analyze-indexed-reduce-fn-macro + ham-fisted.alists/make-prim-array-list hooks.ham-fisted/analyze-make-prim-array-list-macro + ham-fisted.lazy-noncaching/make-readonly-list hooks.ham-fisted/analyze-indexed-make-list-macro}}} diff --git a/resources/clj-kondo.exports/cnuernber/ham-fisted/hooks/ham_fisted.clj_kondo b/resources/clj-kondo.exports/cnuernber/ham-fisted/hooks/ham_fisted.clj_kondo new file mode 100644 index 0000000..fa647fa --- /dev/null +++ b/resources/clj-kondo.exports/cnuernber/ham-fisted/hooks/ham_fisted.clj_kondo @@ -0,0 +1,149 @@ +(ns hooks.ham-fisted + (:require [clj-kondo.hooks-api :as api])) + +(defn node-value + [node] + (when node + (api/sexpr node))) + +(defn analyze-hlet-macro + [{:keys [:node]}] + (let [[bindings & body] (rest (:children node)) + new-node (api/list-node + (list* + (api/token-node 'clojure.core/let) + (api/vector-node + (concat + [(api/token-node 'dbls) + (api/token-node 'clojure.core/vec) + (api/token-node 'lngs) + (api/token-node 'clojure.core/vec) + (api/token-node 'lng-fns) + (api/token-node 'clojure.core/vec) + (api/token-node 'dbl-fns) + (api/token-node 'clojure.core/vec) + (api/token-node 'obj-fns) + (api/token-node 'clojure.core/vec)] + (:children bindings))) + body))] + {:node new-node})) + +(defn analyze-1-arg-fn-macro + [{:keys [:node]}] + (let [[arg1 & body] (rest (:children node)) + new-node (api/list-node + (list* + (api/token-node 'clojure.core/fn) + (api/vector-node [arg1]) + body))] + {:node new-node})) + +(defn analyze-2-arg-fn-macro + [{:keys [:node]}] + (let [[arg1 arg2 & body] (rest (:children node)) + new-node (api/list-node + (list* + (api/token-node 'clojure.core/fn) + (api/vector-node [arg1 arg2]) + body))] + {:node new-node})) + +(defn analyze-indexed-reduce-fn-macro + [{:keys [:node]}] + (let [[acc-arg idx-arg obj-arg & body] (rest (:children node)) + new-node (api/list-node + (list* + (api/token-node 'clojure.core/fn) + (api/vector-node [acc-arg + (api/vector-node [idx-arg obj-arg])]) + body))] + {:node new-node})) + +(defn analyze-indexed-make-list-macro + [{:keys [:node]}] + (let [children (rest (:children node)) + _input-args (drop-last 3 children) + [nel-arg idx-arg & body] (take-last 3 children) + new-node (api/list-node + (list + (api/token-node 'clojure.core/map) + (api/list-node + (list* + (api/token-node 'clojure.core/fn) + (api/vector-node [idx-arg]) + body)) + (api/list-node + (list + (api/token-node 'clojure.core/range) + (api/token-node nel-arg)))))] + {:node new-node})) + +(defn analyze-make-prim-array-list-macro + [{:keys [:node]}] + (let [[lname ary-tag iface getname setname addname set-cast-fn get-cast-fn obj-cast-fn add-all-reduce] (rest (:children node)) + new-node (api/list-node + (list + (api/token-node 'clojure.core/deftype) + (vary-meta lname assoc :tag (node-value ary-tag)) + (api/vector-node + [(api/token-node 'data) + (api/token-node 'n-elems) + (api/token-node 'm)]) + iface + (api/list-node + (list + getname + (api/vector-node + [(api/token-node '_) + (api/token-node 'idx)]) + (api/list-node + (list + get-cast-fn + (api/list-node + (list + (api/token-node 'clojure.core/aget) + (api/token-node 'data) + (api/token-node 'idx))))))) + (api/list-node + (list + setname + (api/vector-node + [(api/token-node '_) + (api/token-node 'idx) + (api/token-node 'v)]) + (api/list-node + (list + (api/token-node 'clojure.core/aset) + (api/token-node 'data) + (api/token-node 'idx) + (api/list-node + (list + set-cast-fn + (api/token-node 'v))))))) + (api/list-node + (list + addname + (api/vector-node + [(api/token-node '_) + (api/token-node 'v)]) + (api/list-node + (list + (api/token-node 'clojure.core/aset) + (api/token-node 'data) + (api/token-node 'n-elems) + (api/list-node + (list + obj-cast-fn + (api/token-node 'v))))))) + (api/list-node + (list + (api/token-node 'addAllReducible) + (api/vector-node + [(api/token-node 'this) + (api/token-node 'coll)]) + (api/list-node + (list + add-all-reduce + (api/token-node 'this) + (api/token-node 'coll)))))))] + {:node new-node})) diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..e2baff9 --- /dev/null +++ b/scripts/lint @@ -0,0 +1,5 @@ +#!/bin/bash + +scripts/compile +clojure -M:dev:test:clj-kondo --copy-configs --dependencies --parallel --lint "$(clojure -A:dev:test -Spath)" +clojure -M:dev:test:clj-kondo --lint "src:test" --fail-level "error" diff --git a/src/ham_fisted/api.clj b/src/ham_fisted/api.clj index 6e5d446..272123f 100644 --- a/src/ham_fisted/api.clj +++ b/src/ham_fisted/api.clj @@ -1141,7 +1141,7 @@ ham_fisted.PersistentHashMap ([f m1] m1) ([f m1 m2] (map-union f m1 m2)) ([f m1 m2 & args] - (union-reduce-maps f (apply-concat [(map-union f m1 m2)] args)))) + (union-reduce-maps f (apply-concat [[(map-union f m1 m2)] args])))) (defn memoize @@ -2418,7 +2418,6 @@ ham-fisted.api> (binary-search data 1.1 nil) (defn take-last "Take the last N values of the collection. If the input is random-access, the result will be random-access." - ([n] (clojure.core/take-last n)) ([n coll] (when coll (let [coll (->reducible coll)] diff --git a/src/ham_fisted/lazy_noncaching.clj b/src/ham_fisted/lazy_noncaching.clj index e69e372..16677e6 100644 --- a/src/ham_fisted/lazy_noncaching.clj +++ b/src/ham_fisted/lazy_noncaching.clj @@ -272,7 +272,7 @@ "Lazy nonaching map but f simply gets a single random-access list of arguments. The argument list may be mutably updated between calls." ([f c1] - (let [rdc (fn [rfn acc] (reduce c1 (fn [acc v] (rfn acc (f [v])))))] + (let [rdc (fn [rfn acc] (reduce (fn [acc v] (rfn acc (f [v]))) acc c1))] (if-let [c1 (as-random-access c1)] (reify IMutList (size [this] (.size c1)) diff --git a/src/ham_fisted/protocols.clj b/src/ham_fisted/protocols.clj index 19a524d..5fb99dd 100644 --- a/src/ham_fisted/protocols.clj +++ b/src/ham_fisted/protocols.clj @@ -4,7 +4,7 @@ [java.util.function DoubleConsumer] [java.util Map] [ham_fisted Sum Sum$SimpleSum Reducible IFnDef$ODO ParallelOptions - Reductions]) + Reductions IMutList]) (:refer-clojure :exclude [reduce set?])) diff --git a/test/ham_fisted/api_test.clj b/test/ham_fisted/api_test.clj index dc92409..9ef4341 100644 --- a/test/ham_fisted/api_test.clj +++ b/test/ham_fisted/api_test.clj @@ -13,19 +13,26 @@ (deftest parallism-primitives-pass-errors - (is (thrown? Exception (count (hamf/upmap - (fn [^long idx] - (when (== idx 77) (throw (Exception. "Error!!"))) idx) - (range 100))))) - (is (thrown? Exception (count (hamf/pmap (fn [^long idx] - (when (== idx 77) (throw (Exception. "Error!!"))) idx) - (range 100))))) - (is (thrown? Exception (hamf/upgroups (fn [^long sidx ^long eidx] - (when (>= sidx 70) - (throw (Exception. "Error!!"))) sidx)))) - (is (thrown? Exception (hamf/pgroups (fn [^long sidx ^long eidx] - (when (>= sidx 70) - (throw (Exception. "Error!!"))) sidx))))) + (is (thrown-with-msg? Exception #"Error!!" + (doall (hamf/upmap + (fn [^long idx] + (when (== idx 77) (throw (Exception. "Error!!"))) idx) + (range 100))))) + (is (thrown-with-msg? Exception #"Error!!" + (doall (hamf/pmap (fn [^long idx] + (when (== idx 77) (throw (Exception. "Error!!"))) idx) + (range 100))))) + (is (thrown-with-msg? Exception #"Error!!" + (doall (hamf/upgroups 1000 (fn [^long sidx ^long eidx] + (when (>= sidx 10) + (throw (Exception. "Error!!"))) + sidx) + {:batch-size 100})))) + (is (thrown-with-msg? Exception #"Error!!" + (doall (hamf/pgroups 1000 (fn [^long sidx ^long eidx] + (when (>= sidx 10) + (throw (Exception. "Error!!"))) sidx) + {:batch-size 100}))))) (deftest group-by-nil diff --git a/test/ham_fisted/defprotocol_test.clj b/test/ham_fisted/defprotocol_test.clj index 1d2cd6c..b9e828b 100644 --- a/test/ham_fisted/defprotocol_test.clj +++ b/test/ham_fisted/defprotocol_test.clj @@ -109,11 +109,13 @@ (is (= "two-arg baz!" (baz obj nil))) (is (thrown? AbstractMethodError (baz obj))))) (testing "error conditions checked when defining protocols" - (is (thrown-with-cause-msg? + (is #_{:clj-kondo/ignore [:unresolved-symbol]} + (thrown-with-cause-msg? Exception #"Definition of function m in protocol badprotdef must take at least one arg." (eval '(defprotocol badprotdef (m []))))) - (is (thrown-with-cause-msg? + (is #_{:clj-kondo/ignore [:unresolved-symbol]} + (thrown-with-cause-msg? Exception #"Function m in protocol badprotdef was redefined. Specify all arities in single definition." (eval '(defprotocol badprotdef (m [this arg]) (m [this arg1 arg2])))))) @@ -121,7 +123,8 @@ (eval '(defprotocol Elusive (old-method [x]))) (eval '(defprotocol Elusive (new-method [x]))) (is (= :new-method (eval '(new-method (reify Elusive (new-method [x] :new-method)))))) - (is (fails-with-cause? IllegalArgumentException #"No method of interface: .*\.Elusive found for function: old-method of protocol: Elusive \(The protocol method may have been defined before and removed\.\)" + (is #_{:clj-kondo/ignore [:unresolved-symbol]} + (fails-with-cause? IllegalArgumentException #"No method of interface: .*\.Elusive found for function: old-method of protocol: Elusive \(The protocol method may have been defined before and removed\.\)" (eval '(old-method (reify Elusive (new-method [x] :new-method)))))))) (deftype HasMarkers [] @@ -168,12 +171,14 @@ (deftest illegal-extending (testing "you cannot extend a protocol to a type that implements the protocol inline" - (is (fails-with-cause? IllegalArgumentException #".*HasProtocolInline already directly implements interface" + (is #_{:clj-kondo/ignore [:unresolved-symbol]} + (fails-with-cause? IllegalArgumentException #".*HasProtocolInline already directly implements interface" (eval '(extend ham_fisted.defprotocol_test.HasProtocolInline ham-fisted.defprotocol-test.examples/ExampleProtocol {:foo (fn [_] :extended)}))))) (testing "you cannot extend to an interface" - (is (fails-with-cause? IllegalArgumentException #"interface ham_fisted.defprotocol_test.examples.ExampleProtocol is not a protocol" + (is #_{:clj-kondo/ignore [:unresolved-symbol]} + (fails-with-cause? IllegalArgumentException #"interface ham_fisted.defprotocol_test.examples.ExampleProtocol is not a protocol" (eval '(extend ham_fisted.defprotocol_test.HasProtocolInline ham_fisted.defprotocol_test.examples.ExampleProtocol {:foo (fn [_] :extended)})))))) @@ -233,15 +238,4 @@ (defprotocol P (^ISeq f [_])) -(ns ham-fisted.defprotocol-test.other - (:use clojure.test) - (:require [ham-fisted.defprotocol :refer [defprotocol extend-type extend extend-protocol satisfies? extends?]]) - (:refer-clojure :exclude [defprotocol extend-type extend extend-protocol satisfies? extends?])) - -(defn cf [val] - (let [aseq (ham-fisted.defprotocol-test/f val)] - (count aseq))) -(extend-protocol ham-fisted.defprotocol-test/P String - (f [s] (seq s))) -(deftest test-resolve-type-hints-in-protocol-methods - (is (= 4 (cf "test")))) +;;; continues in defprotocol_test/other.clj diff --git a/test/ham_fisted/defprotocol_test/hash_collisions.clj b/test/ham_fisted/defprotocol_test/hash_collisions_test.clj similarity index 91% rename from test/ham_fisted/defprotocol_test/hash_collisions.clj rename to test/ham_fisted/defprotocol_test/hash_collisions_test.clj index 7dcf0ce..cc7b195 100644 --- a/test/ham_fisted/defprotocol_test/hash_collisions.clj +++ b/test/ham_fisted/defprotocol_test/hash_collisions_test.clj @@ -1,5 +1,7 @@ -(ns ham_fisted.protocols-test.hash-collisions - (:use clojure.test)) +(ns ham-fisted.defprotocol-test.hash-collisions-test + (:refer-clojure :exclude [defprotocol extend-type extend extend-protocol satisfies? extends?]) + (:require [clojure.test :refer [deftest is]] + [ham-fisted.defprotocol :refer [defprotocol extend-type extend extend-protocol satisfies? extends?]])) (defprotocol TestProtocolA (method-a [this] "Test method A")) diff --git a/test/ham_fisted/defprotocol_test/other_test.clj b/test/ham_fisted/defprotocol_test/other_test.clj new file mode 100644 index 0000000..9a8fc6d --- /dev/null +++ b/test/ham_fisted/defprotocol_test/other_test.clj @@ -0,0 +1,14 @@ +(ns ham-fisted.defprotocol-test.other-test + (:refer-clojure :exclude [defprotocol extend-type extend extend-protocol satisfies? extends?]) + (:require [clojure.test :refer [deftest is]] + [ham-fisted.defprotocol :refer [defprotocol extend-type extend extend-protocol satisfies? extends?]] + [ham-fisted.defprotocol-test]) + (:refer-clojure :exclude [defprotocol extend-type extend extend-protocol satisfies? extends?])) + +(defn cf [val] + (let [aseq (ham-fisted.defprotocol-test/f val)] + (count aseq))) +(extend-protocol ham-fisted.defprotocol-test/P String + (f [s] (seq s))) +(deftest test-resolve-type-hints-in-protocol-methods + (is (= 4 (cf "test"))))