Skip to content

Commit

Permalink
#917 Static method fn (#939)
Browse files Browse the repository at this point in the history
  • Loading branch information
borkdude authored Oct 10, 2024
1 parent 54a2ca9 commit 67b2626
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 39 deletions.
4 changes: 3 additions & 1 deletion .dir-locals.el
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
((clojure-mode
(cider-clojure-cli-aliases . "dev:test")))
(cider-clojure-cli-aliases . "test:dev"))
(clojurec-mode
(cider-clojure-cli-aliases . "test:dev")))
3 changes: 2 additions & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
org.babashka/sci.impl.types {:mvn/version "0.0.2"}}
:aliases
{:examples {:extra-paths ["examples"]}
:dev {:extra-paths ["reflector/src-java11"]}
:dev {:extra-deps {org.clojure/clojure {:mvn/version "1.12.0"}}
:extra-paths ["reflector/src-java11"]}
:test {:extra-paths ["test" "test-resources"]
:extra-deps {org.clojure/clojure {:mvn/version "1.9.0"}
org.clojure/clojurescript {:mvn/version "1.11.132"}
Expand Down
31 changes: 31 additions & 0 deletions src/sci/impl/analyzer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,35 @@
arr)
nil))))))

#?(:clj
(defn analyze-interop-ifn [_ctx expr [^Class clazz meth]]
(let [stack (assoc (meta expr)
:ns @utils/current-ns
:file @utils/current-file)]
(if-let [_fld (try (Reflector/getStaticField ^Class clazz ^String (str meth))
(catch IllegalArgumentException _
nil))]
(sci.impl.types/->Node
(interop/get-static-field clazz (str meth))
stack)
(sci.impl.types/->Node
(fn [& args]
(Reflector/invokeStaticMethod
(.getName clazz) (str meth)
^objects (into-array Object args)))
stack)))))

#?(:clj
(comment
(def meths (.getMethods Integer))
(def with-name (filter (fn [^java.lang.reflect.Method m]
(= "parseInt" (.getName m)))
meths))
(def arities (map (fn [^java.lang.reflect.Method m]
(count (.getParameterTypes m)))
with-name))
))

;; This could be a protocol, but there's not a clear win in doing so:
;; https://github.com/babashka/sci/issues/848
(defn analyze
Expand Down Expand Up @@ -1828,6 +1857,8 @@
(sci.impl.types/->Node
(faster/deref-1 v)
nil))))
(:sci.impl.analyzer/interop-ifn mv)
(analyze-interop-ifn ctx expr v)
:else v))
;; don't evaluate records, this check needs to go before map?
;; since a record is also a map
Expand Down
3 changes: 2 additions & 1 deletion src/sci/impl/evaluator.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
(when (or (not env)
(not (contains? env sym)))
(let [sym (types/eval sym ctx bindings)
res (second (@utils/lookup ctx sym false))]
res (second
(@utils/lookup ctx sym false nil (qualified-symbol? sym)))]
(when-not #?(:cljs (instance? sci.impl.types/NodeR res)
:clj (instance? sci.impl.types.Eval res))
res)))))
Expand Down
52 changes: 26 additions & 26 deletions src/sci/impl/resolve.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
[sci.impl.records :as records]
[sci.impl.types :refer [->Node]]
[sci.impl.utils :as utils :refer [strip-core-ns
ana-macros]])
#?(:clj (:import [sci.impl Reflector])))
ana-macros]]))

(defn throw-error-with-location [msg node]
(utils/throw-error-with-location msg node {:phase "analysis"}))
Expand Down Expand Up @@ -67,29 +66,29 @@
[clazz #?(:clj sym-name
:cljs (.split (str sym-name) "."))]
{:sci.impl.analyzer/static-access true})
(let [stack (assoc (meta sym)
:file @utils/current-file
:ns @utils/current-ns)]
#?(:clj
(->Node
(interop/get-static-field clazz sym-name)
stack) #_(if (str/starts-with? (str sym-name) ".")
::TODO-return-IFn ;; that invokes instance method on class...
#_(let [meths (Reflector/getMethods clazz arg-count method false)])

#?(:clj
;; TODO:
;; - [ ] if sym-name doesn't start with dot, it might be a static method which we need to turn into an IFn
;; - [ ] but if it's a field, we still need to do the field call
;; - [ ] if sym-name starts with dot, it's def. a method call which we need to create an IFn for
(with-meta
[clazz #?(:clj sym-name
:cljs (.split (str sym-name) "."))]
{:sci.impl.analyzer/interop-ifn true})
:cljs
(let [stack (assoc (meta sym)
:file @utils/current-file
:ns @utils/current-ns)
path (.split (str sym-name) ".")
len (alength path)]
(if (== 1 len)
(->Node
(interop/get-static-field clazz sym-name)
stack)
(->Node
(interop/get-static-field clazz sym-name)
stack))
:cljs
(let [path (.split (str sym-name) ".")
len (alength path)]
(if (== 1 len)
(->Node
(interop/get-static-field clazz sym-name)
stack)
(->Node
(interop/get-static-fields clazz path)
stack))))))]))))
(interop/get-static-fields clazz path)
stack))
)))]))))
;; no sym-ns
(or
;; prioritize refers over vars in the current namespace, see 527
Expand Down Expand Up @@ -154,7 +153,8 @@

(defn lookup
([ctx sym call?] (lookup ctx sym call? nil))
([ctx sym call? #?(:clj tag :cljs _tag)]
([ctx sym call? tag] (lookup ctx sym call? tag nil))
([ctx sym call? #?(:clj tag :cljs _tag) only-var?]
(let [bindings (faster/get-2 ctx :bindings)
track-mutable? (faster/get-2 ctx :deftype-fields)]
(or
Expand Down Expand Up @@ -189,7 +189,7 @@
mutable? (vary-meta assoc :mutable true))]
v))]
[k v]))
(when-let [kv (lookup* ctx sym call?)]
(when-let [kv (lookup* ctx sym call? only-var?)]
(when (:check-permissions ctx)
(check-permission! ctx sym kv))
kv)))))
Expand Down
24 changes: 14 additions & 10 deletions test/sci/interop_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -120,23 +120,27 @@
:classes {'java.lang.Class {:class Class
:static-methods {'forName (fn [_Class _forName] :dude)}}}})))))

#?(:clj
(deftest clojure-1_12-interop-test
(is (= [1 2 3] (eval* "(map Integer/parseInt [\"1\" \"2\" \"3\"])")))))

(when-not tu/native?
(deftest exception-data
(testing "top-level interop forms have line and column data"
(letfn [(form-ex-data [form]
(try
(tu/eval* (str form) {:classes {:allow :all
#?@(:clj ['Long Long])}})
(is (= nil "shouldn't reach here"))
(catch #?(:clj Exception :cljs :default) e
(ex-data e))))]
(let [form-ex-data
(fn [form]
(try
(tu/eval* (str form) {:classes {:allow :all
#?@(:clj ['Long Long])}})
(is (= nil "shouldn't reach here") (str form))
(catch #?(:clj Exception :cljs :default) e
(ex-data e))))]
(testing "instance members"
(are [form]
(let [actual (form-ex-data form)]
(and (tu/submap? {:type :sci/error
:line 1
:column 1}
:line 1
:column 1}
actual)
(str/includes? (:message actual) "missingMem")))
'(. 3 missingMem) '(. 3 missingMem 1 2)
Expand All @@ -156,7 +160,7 @@
'(.missingMem Long) '(.missingMem Long 1 2)
'(Long/missingMem) '(Long/missingMem 1 2)
'(. Long -missingMem) '(.-missingMem Long)
'Long/missingMem '(Long/-missingMem))))))))
#_#_'Long/missingMem '(Long/-missingMem))))))))

(deftest syntax-test
(when-not tu/native?
Expand Down

0 comments on commit 67b2626

Please sign in to comment.