(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.source-host
"Recognize native JVM syntax in the caller namespace. Return host
descriptors and ordered source operands; none of those operands is evaluated."
(:require [com.tailrecursion.nanopass :as np]
[com.tailrecursion.blacktide.compiler.languages :as l]
[com.tailrecursion.blacktide.compiler.jvm :as jvm]
[typed.clojure :as-alias t]))
(np/defpass tags {:from [l/JVM Raw] :to [l/JVM Tags] :body true}
[symbol]
(let [tags (:param-tags (meta symbol))]
(if (or (nil? tags) (vector? tags)) tags
(throw (IllegalArgumentException. "param-tags must be a vector")))))
(np/defpass qualified
{:from [l/JVM Raw] :to (t/Option l/JVM-Operation) :body true}
[symbol]
(when (and (symbol? symbol) (namespace symbol))
(when-let [class (jvm/resolved-class (clojure.core/symbol (or (namespace symbol) "")))]
(let [member (name symbol) tags (tags symbol)]
(cond (= member "new") {:op :constructor :class class :tags tags}
(.startsWith member ".") {:op :instance :class class :member (clojure.core/symbol (subs member 1))
:mode :method :tags tags}
(and (clojure.lang.Reflector/getField class member true)
(not (jvm/static-method? class member)))
{:op :static-field :class class :member (clojure.core/symbol member)}
:else {:op :static :class class :member (clojure.core/symbol member) :tags tags})))))
(np/defpass syntax
{:from [l/Bound Forms] :to (t/Option l/JVM-Syntax) :body true}
[forms]
(let [op (first forms) args (subvec forms 1)]
(cond
(= op 'new)
(if-let [class (jvm/resolved-class (first args))]
{:operation {:op :constructor :class class :tags nil} :operands (vec (rest args))}
(throw (IllegalArgumentException. "new requires a class name")))
(= op '.)
(let [[target member & extra] args
_ (when (< (count forms) 3) (throw (IllegalArgumentException. "Malformed member expression")))
class (jvm/resolved-class target)
method? (seq? member)
values (if method? (vec (rest member)) (vec extra))
member (if method? (first member) member)]
(when-not (symbol? member) (throw (IllegalArgumentException. "Expected a Java member name")))
(let [field? (.startsWith (name member) "-")
member (symbol (if field? (subs (name member) 1) (name member)))
mode (cond field? :field (or method? (seq values)) :method :else :member)]
(if class
(if (and (not= mode :method)
(or field? (empty? (jvm/methods class 0 true member))))
{:operation {:op :static-field :class class :member member} :operands []}
{:operation {:op :static :class class :member member :tags nil} :operands values})
{:operation {:op :instance :class nil :member member :mode mode :tags nil}
:operands (into [target] values)})))
(symbol? op)
(cond
(and (nil? (namespace op)) (.startsWith (name op) "."))
(let [target (first args)
target (if (jvm/resolved-class target)
(with-meta (list 'clojure.core/identity target) {:tag 'java.lang.Class}) target)]
(when (empty? args) (throw (IllegalArgumentException. "Instance member requires a receiver")))
(syntax (into ['. target (symbol (subs (name op) 1))] (rest args))))
(.endsWith (name op) ".")
(syntax (into ['new (symbol (subs (str op) 0 (dec (count (str op)))))] args))
:else (when-let [operation (qualified op)]
(when (and (= :instance (:op operation)) (empty? args))
(throw (IllegalArgumentException. "Qualified instance call requires a receiver")))
(when (and (= :static-field (:op operation)) (seq args))
(throw (IllegalArgumentException. "Cannot invoke a static field with arguments")))
{:operation operation :operands args})))))