diff --git a/src/babashka/impl/reify.clj b/src/babashka/impl/reify.clj index 6dca3a52..43f20e45 100644 --- a/src/babashka/impl/reify.clj +++ b/src/babashka/impl/reify.clj @@ -4,129 +4,152 @@ (set! *warn-on-reflection* false) +;; Notes + +;; We abandoned the 'one reify object that implements all interfaces' approach +;; due to false positives. E.g. when you would print a reified object, you would +;; get: 'Not implemented: seq', because print-method thought this object was +;; seqable, while in fact, it wasn't. + +(defn method-or-bust [methods k] + (or (get methods k) + (throw (UnsupportedOperationException. "Method not implemented: " k)))) + (defmacro gen-reify-combos "Generates pre-compiled reify combinations" [methods] - (let [prelude ['reify - 'sci.impl.types.IReified - '(getInterfaces [this] - interfaces) - '(getMethods [this] - methods) - '(getProtocols [this] - protocols) - 'java.lang.Object - '(toString [this] - (if-let [m (get methods 'toString)] - (m this) - (str (.. this getClass getName) - "@" (Integer/toHexString (.hashCode this)))))]] + (let [prelude '(reify + sci.impl.types.IReified + (getInterfaces [this] + interfaces) + (getMethods [this] + methods) + (getProtocols [this] + protocols))] (list 'fn [{:keys '[interfaces methods protocols]}] - (concat prelude - (mapcat (fn [[clazz methods]] - (cons - clazz - (mapcat - (fn [[meth arities]] - (map - (fn [arity] - (list meth arity - (list* - (list 'or (list 'get 'methods (list 'quote meth)) - `(throw (new Exception (str "Not implemented: " - ~(str meth))))) - arity))) - arities)) - methods))) - methods))))) + `(cond ~'(empty? interfaces) ~prelude + ~'(> (count interfaces) + 1) + (throw (new Exception "Babashka currently does not support reifying more than one interface.")) + :else + (case (.getName ~(with-meta '(first interfaces) + {:tag 'Class})) + ~@(mapcat + (fn [[clazz methods]] + (list + (str clazz) + (concat prelude + (cons clazz + (mapcat + (fn [[meth arities]] + (map + (fn [arity] + (list meth arity + (list* + (list 'or (list 'get 'methods (list 'quote meth)) + `(throw (new Exception (str "Not implemented: " + ~(str meth))))) + arity))) + arities)) + methods))))) + methods))))) -#_:clj-kondo/ignore + ;; (require 'clojure.pprint) + ;; (clojure.pprint/pprint + ;; (macroexpand '(gen-reify-combos {java.nio.file.FileVisitor + ;; {preVisitDirectory [[this p attrs]] + ;; postVisitDirectory [[this p attrs]] + ;; visitFile [[this p attrs]]}}))) + + #_:clj-kondo/ignore) (def reify-fn (gen-reify-combos - {java.nio.file.FileVisitor - {preVisitDirectory [[this p attrs]] - postVisitDirectory [[this p attrs]] - visitFile [[this p attrs]]} + {java.lang.Object + {toString [[this]]} + java.nio.file.FileVisitor + {preVisitDirectory [[this p attrs]] + postVisitDirectory [[this p attrs]] + visitFile [[this p attrs]]} - java.io.FileFilter - {accept [[this f]]} + java.io.FileFilter + {accept [[this f]]} - java.io.FilenameFilter - {accept [[this f s]]} + java.io.FilenameFilter + {accept [[this f s]]} - clojure.lang.Associative - {containsKey [[this k]] - entryAt [[this k]] - assoc [[this k v]]} + clojure.lang.Associative + {containsKey [[this k]] + entryAt [[this k]] + assoc [[this k v]]} - clojure.lang.ILookup - {valAt [[this k] [this k default]]} + clojure.lang.ILookup + {valAt [[this k] [this k default]]} - java.util.Map$Entry - {getKey [[this]] - getValue [[this]]} + java.util.Map$Entry + {getKey [[this]] + getValue [[this]]} - clojure.lang.IFn - {applyTo [[this arglist]] - invoke [[this] - [this a1] - [this a1 a2] - [this a1 a2 a3] - [this a1 a2 a3 a4] - [this a1 a2 a3 a4 a5] - [this a1 a2 a3 a4 a5 a6] - [this a1 a2 a3 a4 a5 a6 a7] - [this a1 a2 a3 a4 a5 a6 a7 a8] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20] - [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20 varargs]]} + clojure.lang.IFn + {applyTo [[this arglist]] + invoke [[this] + [this a1] + [this a1 a2] + [this a1 a2 a3] + [this a1 a2 a3 a4] + [this a1 a2 a3 a4 a5] + [this a1 a2 a3 a4 a5 a6] + [this a1 a2 a3 a4 a5 a6 a7] + [this a1 a2 a3 a4 a5 a6 a7 a8] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20 varargs]]} - clojure.lang.IPersistentCollection - {count [[this]] - cons [[this x]] - empty [[this]] - equiv [[this x]]} + clojure.lang.IPersistentCollection + {count [[this]] + cons [[this x]] + empty [[this]] + equiv [[this x]]} - clojure.lang.IReduce - {reduce [[this f]]} + clojure.lang.IReduce + {reduce [[this f]]} - clojure.lang.IReduceInit - {reduce [[this f initial]]} + clojure.lang.IReduceInit + {reduce [[this f initial]]} - clojure.lang.IKVReduce - {kvreduce [[this f initial]]} + clojure.lang.IKVReduce + {kvreduce [[this f initial]]} - clojure.lang.Indexed - {nth [[this n] [this n not-found]]} + clojure.lang.Indexed + {nth [[this n] [this n not-found]]} - clojure.lang.IPersistentMap - {assocEx [[this k v]] - without [[this k]]} + clojure.lang.IPersistentMap + {assocEx [[this k v]] + without [[this k]]} - clojure.lang.IPersistentStack - {peek [[this]] - pop [[this]]} + clojure.lang.IPersistentStack + {peek [[this]] + pop [[this]]} - clojure.lang.Reversible - {rseq [[this]]} + clojure.lang.Reversible + {rseq [[this]]} - clojure.lang.Seqable - {seq [[this]]} + clojure.lang.Seqable + {seq [[this]]} - java.lang.Iterable - {iterator [[this]] - forEach [[this action]]} + java.lang.Iterable + {iterator [[this]] + forEach [[this action]]} - java.util.Iterator - {hasNext [[this]] - next [[this]]}})) + java.util.Iterator + {hasNext [[this]] + next [[this]]}})) diff --git a/test/babashka/reify_test.clj b/test/babashka/reify_test.clj index 5cc24b1e..1e094f5e 100644 --- a/test/babashka/reify_test.clj +++ b/test/babashka/reify_test.clj @@ -2,8 +2,7 @@ (:require [babashka.test-utils :as test-utils] [clojure.edn :as edn] - [clojure.test :as test :refer [deftest is testing]] - [clojure.string :as str])) + [clojure.test :as test :refer [deftest is testing]])) (defn bb [input & args] (edn/read-string @@ -12,15 +11,15 @@ (apply test-utils/bb (when (some? input) (str input)) (map str args)))) (deftest file-filter-test - (testing "reify can handle multiple classes at once" - (is (true? (bb nil " + (is (true? (bb nil " (def filter-obj (reify java.io.FileFilter - (accept [this f] (prn (.getPath f)) true) - java.io.FilenameFilter + (accept [this f] (prn (.getPath f)) true))) +(def filename-filter-obj + (reify java.io.FilenameFilter (accept [this f name] (prn name) true))) (def s1 (with-out-str (.listFiles (clojure.java.io/file \".\") filter-obj))) -(def s2 (with-out-str (.list (clojure.java.io/file \".\") filter-obj))) -(and (pos? (count s1)) (pos? (count s2)))"))))) +(def s2 (with-out-str (.listFiles (clojure.java.io/file \".\") filename-filter-obj))) +(and (pos? (count s1)) (pos? (count s2)))")))) (deftest reify-multiple-arities-test (testing "ILookup" @@ -65,9 +64,4 @@ (def m (reify Object (toString [_] (str :foo)))) (hash m) -")))) - (testing "toString still works when not overriding it" - (is (bb nil " -(def m (reify Object)) -(str m) -")))) +")))))