|
7 | 7 | decimal
|
8 | 8 | fractions
|
9 | 9 | importlib.util
|
| 10 | + inspect |
10 | 11 | math
|
11 | 12 | multiprocessing
|
12 | 13 | os
|
|
6447 | 6448 | (let [name-str (name interface-name)
|
6448 | 6449 | method-sigs (->> methods
|
6449 | 6450 | (map (fn [[method-name args docstring]]
|
6450 |
| - [method-name (conj args 'self) docstring])) |
| 6451 | + [method-name (vec (concat ['self] args)) docstring])) |
6451 | 6452 | (map #(list 'quote %)))]
|
6452 | 6453 | `(def ~interface-name
|
6453 | 6454 | (gen-interface :name ~name-str
|
|
6883 | 6884 |
|
6884 | 6885 | Methods must supply a ``this`` or ``self`` parameter. ``recur`` special forms used in
|
6885 | 6886 | the body of a method should not include that parameter, as it will be supplied
|
6886 |
| - automatically." |
| 6887 | + automatically. |
| 6888 | + |
| 6889 | + .. note:: |
| 6890 | + |
| 6891 | + ``deftype`` creates new types with ``__slots__`` by default. To disable usage |
| 6892 | + of ``__slots__``, provide the ``^{:slots false}`` meta key on the type name. |
| 6893 | + |
| 6894 | + .. code-block:: |
| 6895 | + |
| 6896 | + (deftype ^{:slots false} Point [x y z])" |
6887 | 6897 | [type-name fields & method-impls]
|
6888 | 6898 | (let [ctor-name (with-meta
|
6889 | 6899 | (symbol (str "->" (name type-name)))
|
|
6951 | 6961 | ~@methods)
|
6952 | 6962 | (meta &form))))
|
6953 | 6963 |
|
| 6964 | +;;;;;;;;;;;;; |
| 6965 | +;; Proxies ;; |
| 6966 | +;;;;;;;;;;;;; |
| 6967 | + |
| 6968 | +(def ^:private excluded-proxy-methods |
| 6969 | + #{"__getattribute__" |
| 6970 | + "__init__" |
| 6971 | + "__new__" |
| 6972 | + "__subclasshook__"}) |
| 6973 | + |
| 6974 | +(def ^:private proxy-cache (atom {})) |
| 6975 | + |
| 6976 | +;; One consequence of adhering so closely to the Clojure proxy model is that this |
| 6977 | +;; style of dispatch method doesn't align well with the Basilisp style of defining |
| 6978 | +;; multi-arity methods (which involves creating the "main" entrypoint method which |
| 6979 | +;; dispatches to private implementations for all of the defined arities). |
| 6980 | +;; |
| 6981 | +;; Fortunately, since the public interface of even multi-arity methods is a single |
| 6982 | +;; public method, when callers provide a multi-arity override for such methods, |
| 6983 | +;; only the public entrypoint method is overridden in the proxy mappings. This |
| 6984 | +;; should be a sufficient compromise, but it does mean that the superclass arity |
| 6985 | +;; implementations are never overridden. |
| 6986 | +(defn ^:private proxy-base-methods |
| 6987 | + [base] |
| 6988 | + (->> (inspect/getmembers base inspect/isroutine) |
| 6989 | + (remove (comp excluded-proxy-methods first)) |
| 6990 | + (mapcat (fn [[method-name method]] |
| 6991 | + (let [meth-sym (symbol method-name) |
| 6992 | + meth `(fn ~meth-sym [~'self & args#] |
| 6993 | + (if-let [override (get (.- ~'self ~'-proxy-mappings) ~method-name)] |
| 6994 | + (apply override ~'self args#) |
| 6995 | + (-> (.- ~'self __class__) |
| 6996 | + (python/super ~'self) |
| 6997 | + (.- ~meth-sym) |
| 6998 | + (apply args#))))] |
| 6999 | + [method-name (eval meth *ns*)]))))) |
| 7000 | + |
| 7001 | +(defn ^:private proxy-type |
| 7002 | + "Generate a proxy class with the given bases." |
| 7003 | + [bases] |
| 7004 | + (let [methods (apply hash-map (mapcat proxy-base-methods bases)) |
| 7005 | + method-names (set (map munge (keys methods))) |
| 7006 | + base-methods {"__init__" (fn __init__ [self proxy-mappings & args] |
| 7007 | + (apply (.- (python/super (.- self __class__) self) __init__) args) |
| 7008 | + (. self (_set_proxy_mappings proxy-mappings)) |
| 7009 | + nil) |
| 7010 | + "_get_proxy_mappings" (fn _get_proxy_mappings [self] |
| 7011 | + (.- self -proxy-mappings)) |
| 7012 | + "_set_proxy_mappings" (fn _set_proxy_mappings [self proxy-mappings] |
| 7013 | + (let [provided-methods (set (keys proxy-mappings))] |
| 7014 | + (when-not (.issubset provided-methods method-names) |
| 7015 | + (throw |
| 7016 | + (ex-info "Proxy override methods must correspond to methods on the declared supertypes" |
| 7017 | + {:expected method-names |
| 7018 | + :given provided-methods |
| 7019 | + :diff (.difference provided-methods method-names)})))) |
| 7020 | + (set! (.- self -proxy-mappings) proxy-mappings) |
| 7021 | + nil) |
| 7022 | + "_update_proxy_mappings" (fn _update_proxy_mappings [self proxy-mappings] |
| 7023 | + (let [updated-mappings (->> proxy-mappings |
| 7024 | + (reduce* (fn [m [k v]] |
| 7025 | + (if v |
| 7026 | + (assoc! m k v) |
| 7027 | + (dissoc! m k))) |
| 7028 | + (transient (.- self -proxy-mappings))) |
| 7029 | + (persistent!))] |
| 7030 | + (. self (_set_proxy_mappings updated-mappings)) |
| 7031 | + nil)) |
| 7032 | + "__setattr__" (fn __setattr__ [self attr val] |
| 7033 | + "Override __setattr__ specifically for _proxy_mappings." |
| 7034 | + (if (= attr "_proxy_mappings") |
| 7035 | + (. python/object __setattr__ self attr val) |
| 7036 | + ((.- (python/super (.- self __class__) self) __setattr__) attr val))) |
| 7037 | + "_proxy_mappings" nil} |
| 7038 | + |
| 7039 | + ;; Remove Python ``object`` from the bases if it is present to avoid errors |
| 7040 | + ;; about creating a consistent MRO for the given bases |
| 7041 | + proxy-bases (concat (remove #{python/object} bases) [basilisp.lang.interfaces/IProxy])] |
| 7042 | + (python/type (basilisp.lang.util/genname "Proxy") |
| 7043 | + (python/tuple proxy-bases) |
| 7044 | + (python/dict (merge methods base-methods))))) |
| 7045 | + |
| 7046 | +(defn get-proxy-class |
| 7047 | + "Given zero or more base classes, return a proxy class for the given classes. |
| 7048 | + |
| 7049 | + If no classes, Python's ``object`` will be used as the superclass. |
| 7050 | + |
| 7051 | + Generated classes are cached, such that the same set of base classes will always |
| 7052 | + return the same resulting proxy class." |
| 7053 | + [& bases] |
| 7054 | + (let [base-set (if (seq bases) |
| 7055 | + (set bases) |
| 7056 | + #{python/object})] |
| 7057 | + (-> (swap! proxy-cache (fn [cache] |
| 7058 | + (if (get cache base-set) |
| 7059 | + cache |
| 7060 | + (assoc cache base-set (proxy-type base-set))))) |
| 7061 | + (get base-set)))) |
| 7062 | + |
| 7063 | +(defn proxy-mappings |
| 7064 | + "Return the current method map for the given proxy. |
| 7065 | + |
| 7066 | + Throws an exception if ``proxy`` is not a proxy." |
| 7067 | + [proxy] |
| 7068 | + (if-not (instance? basilisp.lang.interfaces/IProxy proxy) |
| 7069 | + (throw |
| 7070 | + (ex-info "Cannot get proxy mappings from object which does not implement IProxy" |
| 7071 | + {:obj proxy})) |
| 7072 | + (. proxy (_get-proxy-mappings)))) |
| 7073 | + |
| 7074 | +(defn construct-proxy |
| 7075 | + "Construct an instance of the proxy class ``c`` with the given constructor arguments. |
| 7076 | + |
| 7077 | + Throws an exception if ``c`` is not a subclass of |
| 7078 | + :py:class:`basilisp.lang.interfaces.IProxy`. |
| 7079 | + |
| 7080 | + .. note:: |
| 7081 | + |
| 7082 | + In JVM Clojure, this function is useful for selecting a specific constructor based |
| 7083 | + on argument count, but Python does not support multi-arity methods (including |
| 7084 | + constructors), so this is likely of limited use." |
| 7085 | + [c & ctor-args] |
| 7086 | + (if-not (python/issubclass c basilisp.lang.interfaces/IProxy) |
| 7087 | + (throw |
| 7088 | + (ex-info "Cannot construct instance of class which is not a subclass of IProxy" |
| 7089 | + {:class c :args ctor-args})) |
| 7090 | + (apply c {} ctor-args))) |
| 7091 | + |
| 7092 | +(defn init-proxy |
| 7093 | + "Set the current proxy method map for the given proxy. |
| 7094 | + |
| 7095 | + Method maps are maps of string method names to their implementations as Basilisp |
| 7096 | + functions. |
| 7097 | + |
| 7098 | + Throws an exception if ``proxy`` is not a proxy." |
| 7099 | + [proxy mappings] |
| 7100 | + (if-not (instance? basilisp.lang.interfaces/IProxy proxy) |
| 7101 | + (throw |
| 7102 | + (ex-info "Cannot set proxy mappings for an object which does not implement IProxy" |
| 7103 | + {:obj proxy})) |
| 7104 | + (do |
| 7105 | + (. proxy (_set-proxy-mappings mappings)) |
| 7106 | + proxy))) |
| 7107 | + |
| 7108 | +(defn update-proxy |
| 7109 | + "Update the current proxy method map for the given proxy. |
| 7110 | + |
| 7111 | + Method maps are maps of string method names to their implementations as Basilisp |
| 7112 | + functions. If ``nil`` is passed in place of a function for a method, that method will |
| 7113 | + revert to its default behavior. |
| 7114 | + |
| 7115 | + Throws an exception if ``proxy`` is not a proxy." |
| 7116 | + [proxy mappings] |
| 7117 | + (if-not (instance? basilisp.lang.interfaces/IProxy proxy) |
| 7118 | + (throw |
| 7119 | + (ex-info "Cannot update proxy mappings for object which does not implement IProxy" |
| 7120 | + {:obj proxy})) |
| 7121 | + (do |
| 7122 | + (. proxy (_update-proxy-mappings mappings)) |
| 7123 | + proxy))) |
| 7124 | + |
| 7125 | +(defmacro proxy |
| 7126 | + "Create a new proxy class instance. |
| 7127 | + |
| 7128 | + The proxy class may implement 0 or more interface (or subclass 0 or more classes), |
| 7129 | + which are given as the vector ``class-and-interfaces``. If 0 such supertypes are |
| 7130 | + provided, Python's ``object`` type will be used. |
| 7131 | + |
| 7132 | + If the supertype constructors take arguments, those arguments are given in the |
| 7133 | + potentially empty vector ``args``. |
| 7134 | + |
| 7135 | + The remaining forms (if any) should be method overrides for any methods of the |
| 7136 | + declared classes and interfaces. Not every method needs to be overridden. Override |
| 7137 | + declarations may be multi-arity to simulate multi-arity methods. Overrides need |
| 7138 | + not include ``this``, as it will be automatically added and is available within |
| 7139 | + all proxy methods. Proxy methods may access the proxy superclass using the |
| 7140 | + :lpy:fn:`proxy-super` macro. |
| 7141 | + |
| 7142 | + Overrides take the following form:: |
| 7143 | + |
| 7144 | + (single-arity [] |
| 7145 | + ...) |
| 7146 | + |
| 7147 | + (multi-arity |
| 7148 | + ([] ...) |
| 7149 | + ([arg1] ...) |
| 7150 | + ([arg1 & others] ...)) |
| 7151 | + |
| 7152 | + .. note:: |
| 7153 | + |
| 7154 | + Proxy override methods can be defined with Python keyword argument support since |
| 7155 | + they are just standard Basilisp functions. See :ref:`basilisp_functions_with_kwargs` |
| 7156 | + for more information. |
| 7157 | + |
| 7158 | + .. warning:: |
| 7159 | + |
| 7160 | + The ``proxy`` macro does not verify that the provided override implementations |
| 7161 | + arities match those of the method being overridden. |
| 7162 | + |
| 7163 | + .. warning:: |
| 7164 | + |
| 7165 | + Attempting to create a proxy with multiple superclasses defined with ``__slots__`` |
| 7166 | + may fail with a ``TypeError``. If you control any of the designated superclasses, |
| 7167 | + removing conflicting ``__slots__`` should enable creation of the proxy type." |
| 7168 | + [class-and-interfaces args & fs] |
| 7169 | + (let [formatted-single (fn [method-name [arg-vec & body]] |
| 7170 | + (apply list |
| 7171 | + 'fn |
| 7172 | + method-name |
| 7173 | + (with-meta (vec (concat ['this] arg-vec)) (meta arg-vec)) |
| 7174 | + body)) |
| 7175 | + formatted-multi (fn [method-name & arities] |
| 7176 | + (apply list |
| 7177 | + 'fn |
| 7178 | + method-name |
| 7179 | + (map (fn [[arg-vec & body]] |
| 7180 | + (apply list |
| 7181 | + (with-meta (vec (concat ['this] arg-vec)) (meta arg-vec)) |
| 7182 | + body)) |
| 7183 | + arities))) |
| 7184 | + methods (map (fn [[method-name & body :as form]] |
| 7185 | + [(munge method-name) |
| 7186 | + (with-meta |
| 7187 | + (if (vector? (first body)) |
| 7188 | + (formatted-single method-name body) |
| 7189 | + (apply formatted-multi method-name body)) |
| 7190 | + (meta form))]) |
| 7191 | + fs) |
| 7192 | + method-map (reduce* (fn [m [method-name method]] |
| 7193 | + (if-let [existing-method (get m method-name)] |
| 7194 | + (throw |
| 7195 | + (ex-info "Cannot define proxy class with duplicate method" |
| 7196 | + {:method-name method-name |
| 7197 | + :impls [existing-method method]})) |
| 7198 | + (assoc m method-name method))) |
| 7199 | + {} |
| 7200 | + methods)] |
| 7201 | + `((get-proxy-class ~@class-and-interfaces) ~method-map ~@args))) |
| 7202 | + |
| 7203 | +(defmacro proxy-super |
| 7204 | + "Macro which expands to a call to the method named ``meth`` on the superclass |
| 7205 | + with the provided ``args``. |
| 7206 | + |
| 7207 | + Note this macro explicitly captures the implicit ``this`` parameter added to proxy |
| 7208 | + methods." |
| 7209 | + [meth & args] |
| 7210 | + `(. (~'python/super (.- ~'this ~'__class__) ~'this) (~meth ~@args))) |
| 7211 | + |
6954 | 7212 | ;;;;;;;;;;;;;
|
6955 | 7213 | ;; Records ;;
|
6956 | 7214 | ;;;;;;;;;;;;;
|
|
0 commit comments