🐛 Make clj/jvm record impl behave the same as cljs/js

This commit is contained in:
Andrey Antukh 2023-08-23 18:36:34 +02:00
parent e4ec954b8c
commit 3292e7b923

View file

@ -7,7 +7,10 @@
(ns app.common.record (ns app.common.record
"A collection of helpers and macros for defien a penpot customized record types." "A collection of helpers and macros for defien a penpot customized record types."
(:refer-clojure :exclude [defrecord assoc! clone]) (:refer-clojure :exclude [defrecord assoc! clone])
#?(:cljs (:require-macros [app.common.record]))) #?(:cljs (:require-macros [app.common.record]))
#?(:clj
(:import java.util.Map
java.util.Map$Entry)))
#_:clj-kondo/ignore #_:clj-kondo/ignore
(defmacro caching-hash (defmacro caching-hash
@ -35,16 +38,12 @@
fields))) fields)))
#?(:clj #?(:clj
(defn emit-extend (defn emit-impl-js
[env tagname fields impls] [tagname base-fields]
(let [base-fields (mapv #(with-meta % nil) fields) (let [fields (conj base-fields '$meta '$extmap (with-meta '$hash {:mutable true}))
fields (conj base-fields '$meta '$extmap (with-meta '$hash {:mutable true}))
key-sym (gensym "key-") key-sym (gensym "key-")
val-sym (gensym "val-") val-sym (gensym "val-")
this-sym (with-meta (gensym "this-") {:tag tagname}) this-sym (with-meta 'this {:tag tagname})]
other-sym (gensym "other-")
pr-open (str "#" (-> env :ns :name) "." (name tagname) "{")]
(concat impls
['cljs.core/ICloneable ['cljs.core/ICloneable
`(~'-clone [~this-sym] `(~'-clone [~this-sym]
(new ~tagname ~@(generate-field-access this-sym val-sym fields))) (new ~tagname ~@(generate-field-access this-sym val-sym fields)))
@ -59,16 +58,16 @@
(. ~this-sym ~'-$hash))) (. ~this-sym ~'-$hash)))
'cljs.core/IEquiv 'cljs.core/IEquiv
`(~'-equiv [~this-sym ~other-sym] `(~'-equiv [~this-sym ~val-sym]
(and (some? ~other-sym) (and (some? ~val-sym)
(identical? (.-constructor ~this-sym) (identical? (.-constructor ~this-sym)
(.-constructor ~other-sym)) (.-constructor ~val-sym))
~@(map (fn [field] ~@(map (fn [field]
`(= (.. ~this-sym ~(property-symbol field)) `(= (.. ~this-sym ~(property-symbol field))
(.. ~(with-meta other-sym {:tag tagname}) ~(property-symbol field)))) (.. ~(with-meta val-sym {:tag tagname}) ~(property-symbol field))))
base-fields) base-fields)
(= (. ~this-sym ~'-$extmap) (= (. ~this-sym ~'-$extmap)
(. ~(with-meta other-sym {:tag tagname}) ~'-$extmap)))) (. ~(with-meta val-sym {:tag tagname}) ~'-$extmap))))
'cljs.core/IMeta 'cljs.core/IMeta
`(~'-meta [~this-sym] (. ~this-sym ~'-$meta)) `(~'-meta [~this-sym] (. ~this-sym ~'-$meta))
@ -79,14 +78,14 @@
(generate-field-access this-sym val-sym)))) (generate-field-access this-sym val-sym))))
'cljs.core/ILookup 'cljs.core/ILookup
`(~'-lookup `(~'-lookup [~this-sym k#]
([~this-sym k#]
(cljs.core/-lookup ~this-sym k# nil)) (cljs.core/-lookup ~this-sym k# nil))
([~this-sym ~key-sym else#]
`(~'-lookup [~this-sym ~key-sym else#]
(case ~key-sym (case ~key-sym
~@(mapcat (fn [f] [(keyword f) `(. ~this-sym ~(property-symbol f))]) ~@(mapcat (fn [f] [(keyword f) `(. ~this-sym ~(property-symbol f))])
base-fields) base-fields)
(cljs.core/get (. ~this-sym ~'-$extmap) ~key-sym else#)))) (cljs.core/get (. ~this-sym ~'-$extmap) ~key-sym else#)))
'cljs.core/ICounted 'cljs.core/ICounted
`(~'-count [~this-sym] `(~'-count [~this-sym]
@ -166,33 +165,194 @@
'cljs.core/IKVReduce 'cljs.core/IKVReduce
`(~'-kv-reduce [~this-sym f# init#] `(~'-kv-reduce [~this-sym f# init#]
(reduce (fn [ret# [~key-sym v#]] (f# ret# ~key-sym v#)) init# ~this-sym)) (reduce (fn [ret# [~key-sym v#]] (f# ret# ~key-sym v#)) init# ~this-sym))])))
'cljs.core/IPrintWithWriter #?(:clj
`(~'-pr-writer [~this-sym writer# opts#] (defn emit-impl-jvm
(let [pr-pair# (fn [keyval#] [tagname base-fields]
(cljs.core/pr-sequential-writer writer# (~'js* "cljs.core.pr_writer") (let [fields (conj base-fields '$meta '$extmap (with-meta '$hash {:unsynchronized-mutable true}))
"" " " "" opts# keyval#))] key-sym 'key
(cljs.core/pr-sequential-writer val-sym 'val
writer# pr-pair# ~pr-open ", " "}" opts# this-sym (with-meta 'this {:tag tagname})]
(concat [~@(for [f base-fields]
`(vector ~(keyword f) (. ~this-sym ~(property-symbol f))))]
(. ~this-sym ~'-$extmap)))))
])))) ['clojure.lang.MapEquivalence
'clojure.lang.IPersistentMap
`(~'equiv [~this-sym ~'other]
(and (instance? java.util.Map ~'other) (= (.count ~this-sym) (.size ^Map ~'other))
(every? (fn [^clojure.lang.MapEntry e#]
(let [k# (.key e#)]
(and (.containsKey ^Map ~'other k#)
(= (.val e#) (.get ^Map ~'other k#)))))
(.seq ~this-sym))))
`(~'entryAt [~this-sym ~key-sym]
(let [v# (.valAt ~this-sym ~key-sym ::not-found)]
(when (not= v# ::not-found)
(clojure.lang.MapEntry. ~key-sym v#))))
`(~'valAt [~this-sym ~key-sym]
(.valAt ~this-sym ~key-sym nil))
`(~'valAt [~this-sym ~key-sym ~'not-found]
(case ~key-sym
~@(mapcat (fn [f] [(keyword f) `(. ~this-sym ~(property-symbol f))]) base-fields)
(clojure.core/get (. ~this-sym ~'-$extmap) ~key-sym ~'not-found)))
`(~'count [~this-sym]
(+ ~(count base-fields) (count (. ~this-sym ~'-$extmap))))
`(~'empty [~this-sym]
(new ~tagname ~@(->> (remove #{'$extmap '$hash} fields)
(generate-field-access this-sym nil))
nil nil))
`(~'cons [~this-sym ~val-sym]
(if (instance? java.util.Map$Entry ~val-sym)
(let [^Map$Entry e# ~val-sym]
(.assoc ~this-sym (.getKey e#) (.getValue e#)))
(if (instance? clojure.lang.IPersistentVector ~val-sym)
(if (= 2 (count ~val-sym))
(.assoc ~this-sym (nth ~val-sym 0) (nth ~val-sym 1))
(throw (IllegalArgumentException.
"Vector arg to map conj must be a pair")))
(reduce (fn [^clojure.lang.IPersistentMap m#
^java.util.Map$Entry e#]
(.assoc m# (.getKey e#) (.getValue e#)))
~this-sym
~val-sym))))
`(~'assoc [~this-sym ~key-sym ~val-sym]
(case ~key-sym
~@(mapcat (fn [fld]
[(keyword fld) `(new ~tagname ~@(->> (replace {fld val-sym '$hash nil} fields)
(generate-field-access this-sym val-sym)))])
base-fields)
(new ~tagname ~@(->> (remove #{'$extmap '$hash} fields)
(generate-field-access this-sym val-sym))
(assoc (. ~this-sym ~'-$extmap) ~key-sym ~val-sym)
nil)))
`(~'without [~this-sym ~key-sym]
(case ~key-sym
(~@(map keyword base-fields))
(.assoc ~this-sym ~key-sym nil)
(if-let [extmap1# (. ~this-sym ~'-$extmap)]
(let [extmap2# (.without ^clojure.lang.IPersistentMap extmap1# ~key-sym)]
(if (identical? extmap1# extmap2#)
~this-sym
(new ~tagname ~@(->> (remove #{'$extmap '$hash} fields)
(generate-field-access this-sym val-sym))
(not-empty extmap2#)
nil)))
~this-sym)))
`(~'seq [~this-sym]
(seq (concat [~@(map (fn [f]
`(clojure.lang.MapEntry/create
~(keyword f)
(. ~this-sym ~(property-symbol f))))
base-fields)]
(. ~this-sym ~'-$extmap))))
`(~'iterator [~this-sym]
(clojure.lang.SeqIterator. (.seq ~this-sym)))
'clojure.lang.IFn
`(~'invoke [~this-sym ~key-sym]
(.valAt ~this-sym ~key-sym))
`(~'invoke [~this-sym ~key-sym ~'not-found]
(.valAt ~this-sym ~key-sym ~'not-found))
'java.util.Map
`(~'size [~this-sym]
(clojure.core/count ~this-sym))
`(~'containsKey [~this-sym ~key-sym]
~(if (seq base-fields)
`(case ~key-sym
(~@(map keyword base-fields)) true
(contains? (. ~this-sym ~'-$extmap) ~key-sym))
`(contains? (. ~this-sym ~'-$extmap) ~key-sym)))
`(~'isEmpty [~this-sym]
(zero? (count ~this-sym)))
`(~'keySet [~this-sym]
(throw (UnsupportedOperationException. "not implemented")))
`(~'entrySet [~this-sym]
(throw (UnsupportedOperationException. "not implemented")))
`(~'get [~this-sym ~key-sym]
(.valAt ~this-sym ~key-sym))
`(~'containsValue [~this-sym ~val-sym]
(throw (UnsupportedOperationException. "not implemented")))
`(~'values [~this-sym]
(map val (.seq ~this-sym)))
'java.lang.Object
`(~'equals [~this-sym other#]
(.equiv ~this-sym other#))
`(~'hashCode [~this-sym]
(clojure.lang.APersistentMap/mapHash ~this-sym))
'clojure.lang.IHashEq
`(~'hasheq [~this-sym]
(clojure.core/hash-unordered-coll ~this-sym))
'clojure.lang.IObj
`(~'meta [~this-sym]
(. ~this-sym ~'-$meta))
`(~'withMeta [~this-sym ~val-sym]
(new ~tagname ~@(->> (replace {'$meta val-sym} fields)
(generate-field-access this-sym val-sym))))
])))
(defmacro defrecord (defmacro defrecord
[rsym fields & impls] [rsym fields & impls]
(let [param (gensym "param-") (let [param (gensym "param-")
ks (map keyword fields)] ks (map keyword fields)
(if (:ns &env) fields' (mapv #(with-meta % nil) fields)
nsname (if (:ns &env)
(-> &env :ns :name)
(str *ns*))
ident (str "#" nsname "." (name rsym))]
`(do `(do
(deftype ~rsym ~(into fields ['$meta '$extmap '$hash])) (deftype ~rsym ~(into fields ['$meta '$extmap '$hash])
(extend-type ~rsym ~@(emit-extend &env rsym fields impls)) ~@(if (:ns &env)
(emit-impl-js rsym fields')
(emit-impl-jvm rsym fields'))
~@impls
~@(when (:ns &env)
['cljs.core/IPrintWithWriter
`(~'-pr-writer [~'this writer# opts#]
(let [pr-pair# (fn [keyval#]
(cljs.core/pr-sequential-writer writer# (~'js* "cljs.core.pr_writer")
"" " " "" opts# keyval#))]
(cljs.core/pr-sequential-writer
writer# pr-pair# ~(str ident "{") ", " "}" opts#
(concat [~@(for [f fields']
`(vector ~(keyword f) (. ~'this ~(property-symbol f))))]
(. ~'this ~'-$extmap)))))]))
~@(when-not (:ns &env)
[`(defmethod print-method ~rsym [o# ^java.io.Writer w#]
(.write w# ~(str "#" nsname "." (name rsym)))
(print-method (into {} o#) w#))])
(defn ~(with-meta (symbol (str "pos->" rsym)) (defn ~(with-meta (symbol (str "pos->" rsym))
(assoc (meta rsym) :factory :positional)) (assoc (meta rsym) :factory :positional))
[~@fields] [~@fields']
(new ~rsym ~@(conj fields nil nil nil))) (new ~rsym ~@(conj fields nil nil nil)))
(defn ~(with-meta (symbol (str 'map-> rsym)) (defn ~(with-meta (symbol (str 'map-> rsym))
@ -211,14 +371,7 @@
nil nil
(not-empty extmap#) (not-empty extmap#)
nil))) nil)))
~rsym) ~rsym)))
`(do
(clojure.core/defrecord ~rsym ~fields ~@impls)
(defn ~(with-meta (symbol (str "pos->" rsym))
(assoc (meta rsym) :factory :positional))
[~@(map (fn [f] (vary-meta f dissoc :tag)) fields)]
(new ~rsym ~@(conj fields nil nil)))))))
(defmacro clone (defmacro clone
[ssym] [ssym]