Narrow scope of reify

This commit is contained in:
Michiel Borkent 2021-03-13 12:44:12 +01:00
parent f58748ebbe
commit 8551817724
2 changed files with 133 additions and 116 deletions

View file

@ -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]]}}))

View file

@ -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)
"))))
")))))