Skip to content

Commit 7f97b57

Browse files
authored
Auto transient (#743)
1 parent 899a692 commit 7f97b57

File tree

4 files changed

+77
-28
lines changed

4 files changed

+77
-28
lines changed

src/squint/compiler.cljc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@
297297
(let [expr (list* 'doto {} (map (fn [[k v]]
298298
(list 'clojure.core/unchecked-set k v)) expr))]
299299
(emit expr env)))
300-
(cc/tagged-expr 'object)))
300+
(cc/tagged-expr 'object true)))
301301

302302
(defn emit-set [expr env]
303303
(emit-return

src/squint/compiler_common.cljc

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,23 @@
5757
s)
5858
s))
5959

60-
(defrecord Code [js bool]
60+
(defrecord Code [js tag transient]
6161
Object
6262
(toString [_] js))
6363

64-
(defn tagged-expr [js tag]
65-
(map->Code {:js js
66-
:tag tag}))
64+
(defn tagged-expr
65+
([js tag]
66+
(map->Code {:js js
67+
:tag tag}))
68+
([js tag transient]
69+
(map->Code {:js js
70+
:tag tag
71+
:transient transient})))
6772

6873
(defmethod emit-special 'js* [_ env [_js* template & args :as expr]]
6974
(let [mexpr (meta expr)
7075
tag (:tag mexpr)
76+
transient (:transient mexpr)
7177
template (str template)]
7278
(cond->
7379
(-> (reduce (fn [template substitution]
@@ -76,7 +82,7 @@
7682
template
7783
args)
7884
(emit-return (merge env (meta expr))))
79-
tag (tagged-expr tag))))
85+
tag (tagged-expr tag transient))))
8086

8187
(defn expr-env [env]
8288
(assoc env :context :expr :top-level false))
@@ -380,13 +386,14 @@
380386
lctx (if iife? :return ctx)
381387
res (emit l (assoc env :context lctx))
382388
tag (:tag res)
389+
transient (:transient res)
383390
res (cond-> res
384391
(= :return ctx) (statement))
385392
s (cond-> (str exprs res)
386393
iife?
387394
(wrap-implicit-iife env))]
388395
(cond-> s
389-
tag (tagged-expr tag))))
396+
tag (tagged-expr tag transient))))
390397

391398
(defmethod emit-special 'do [_type env [_ & exprs]]
392399
(emit-do env exprs))
@@ -414,7 +421,8 @@
414421
(-> var->ident
415422
(assoc var-name
416423
(cond-> renamed
417-
tag (vary-meta assoc :tag tag))))]
424+
tag
425+
(vary-meta assoc :tag tag))))]
418426
[(str acc expr) var->ident]))
419427
["" upper-var->ident]
420428
partitioned))
@@ -425,7 +433,8 @@
425433
(emit-do (if iife?
426434
(assoc enc-env :context :return)
427435
enc-env) body))
428-
tag (:tag body)]
436+
tag (:tag body)
437+
transient (:transient body)]
429438
(cond-> (str
430439
bindings
431440
(when loop?
@@ -440,7 +449,7 @@
440449
(wrap-implicit-iife env)
441450
iife?
442451
(emit-return enc-env)
443-
tag (tagged-expr tag))))
452+
tag (tagged-expr tag transient))))
444453

445454
(defmethod emit-special 'let* [_type enc-env [_let bindings & body]]
446455
(emit-let enc-env bindings body false))
@@ -1027,7 +1036,8 @@ break;}" body)
10271036
cherry+interop? (and
10281037
cherry?
10291038
(= "js" ns))
1030-
tag (:tag (meta expr))]
1039+
tag (:tag (meta expr))
1040+
transient (:transient (meta expr))]
10311041
(cond-> (emit-return (str
10321042
(emit fname (expr-env env))
10331043
;; this is needed when calling keywords, symbols, etc. We could
@@ -1043,7 +1053,7 @@ break;}" body)
10431053
args)
10441054
args))))
10451055
env)
1046-
tag (tagged-expr tag))))
1056+
tag (tagged-expr tag transient))))
10471057

10481058
(defmethod emit-special 'letfn* [_ env [_ form & body]]
10491059
(let [gensym (:gensym env)

src/squint/internal/macros.cljc

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,9 @@
668668
{:tag 'string})))
669669

670670
(core/defmacro assoc-inline [x & xs]
671-
(assert (even? (count xs)) "assoc! must be called with and object and an even amount of arguments")
671+
(assert (and (even? (count xs))
672+
(seq xs))
673+
"assoc! must be called with and object and an even + positive amount of arguments")
672674
(if (and (= 'assoc (first &form))
673675
(let [snd (second &form)]
674676
(and (seq? snd)
@@ -683,28 +685,36 @@
683685
emitted (emit x (assoc &env :context :expr))
684686
tag (or (:tag emitted)
685687
(:tag (meta x)))
688+
transient (:transient emitted)
686689
x (with-meta (list 'js* (str emitted))
687-
{:tag tag})]
690+
{:tag tag
691+
:transient transient})]
688692
(if (= 'object tag)
689-
(with-meta
690-
(list* 'js* (str "({...~{},"
691-
(str/join ","
692-
(repeat (/ (count xs) 2) "~{}:~{}"))
693-
"})")
694-
x xs)
695-
{:tag 'object})
693+
(if transient
694+
`(assoc! ~x ~@xs)
695+
(with-meta
696+
(list* 'js* (str "({...~{},"
697+
(str/join ","
698+
(repeat (/ (count xs) 2) "~{}:~{}"))
699+
"})")
700+
x xs)
701+
{:tag 'object
702+
:transient true}))
696703
(let [[fn _ & tail] &form]
697704
(with-meta
698705
(list* fn x tail)
699706
(assoc (meta &form)
700707
:squint.compiler/skip-macro true)))))))
701708

702709
(core/defmacro assoc!-inline [x & xs]
703-
(assert (even? (count xs)) "assoc! must be called with and object and an even amount of arguments")
710+
(assert (and (even? (count xs))
711+
(seq xs))
712+
"assoc! must be called with and object and an even + positive amount of arguments")
704713
(let [emit (-> &env :utils :emit)
705714
emitted (emit x (assoc &env :context :expr))
706715
tag (or (:tag emitted)
707716
(:tag (meta x)))
717+
transient (:transient emitted)
708718
x* x
709719
x (with-meta (list 'js* (str emitted))
710720
{:tag tag})]
@@ -713,10 +723,10 @@
713723
(let [obj-sym (with-meta (gensym)
714724
{:tag tag})]
715725
(with-meta `(^:=> (fn [~obj-sym]
716-
(assoc! ~obj-sym ~@xs)) ~x)
726+
(assoc! ~obj-sym ~@xs)) ~x)
717727
;; TODO: we shouldn't have to add a tag here with function return
718728
;; tag inference, which isn't yet available, but within reach
719-
{:tag tag}))
729+
{:tag tag :transient transient}))
720730
(with-meta
721731
(list* 'js* (str "("
722732
(str/join "," (repeat (/ (count xs) 2) "~{}"))
@@ -727,7 +737,7 @@
727737
`(aset ~x ~k ~v))
728738
(partition 2 xs))
729739
[x]))
730-
{:tag 'object}))
740+
{:tag 'object :transient transient}))
731741
(let [[fn _ & tail] &form]
732742
(with-meta
733743
(list* fn x tail)

test/squint/compiler_test.cljs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2676,11 +2676,40 @@ new Foo();")
26762676
(let [s (jss! "(get {:a 1} :a)")]
26772677
(is (str/includes? s "[\"a\"]")))
26782678
(testing "nested assoc"
2679-
(let [s (jss! '(-> (assoc {} :a :b)
2680-
(assoc :c :d)))]
2679+
(let [s (jss! '((fn [^object x]
2680+
(-> (assoc x :a :b)
2681+
(assoc :c :d))) {}))]
26812682
(is (= 1 (count (re-seq #"\.\.\." s))))
26822683
(is (not (str/includes? s "assoc")))
2683-
(is (eq {:a :b :c :d} (js/eval s)))))))
2684+
(is (eq {:a :b :c :d} (js/eval s))))
2685+
(let [s (jss! '(let [x {:a 1}]
2686+
(assoc (-> (assoc x :b 2) (assoc :c :d)) :e :f)))]
2687+
(is (= 1 (count (re-seq #"\.\.\." s))))
2688+
(is (not (str/includes? s "assoc")))
2689+
(is (str/includes? s "[\"e\"] = \"f\""))
2690+
(is (eq {:a 1 :b 2 :c :d :e :f} (js/eval s))))
2691+
(let [s (jss! '(assoc (let [x 1 o {}] (assoc o :x x)) :b 2))]
2692+
(is (= 1 (count (re-seq #"\.\.\." s))))
2693+
(is (not (str/includes? s "assoc")))
2694+
(is (str/includes? s "[\"b\"] = 2"))
2695+
(is (eq {:x 1 :b 2} (js/eval s))))
2696+
(testing "auto-transient"
2697+
(let [s (jss! '(assoc {:a 1} :b 2))]
2698+
(is (not (str/includes? s "assoc")))
2699+
(is (not (str/includes? s "...")))
2700+
(is (str/includes? s "[\"b\"] = 2")))
2701+
(let [s (jss! '(assoc (assoc! {} :a 1) :b 2))]
2702+
(is (not (str/includes? s "assoc")))
2703+
(is (not (str/includes? s "...")))
2704+
(is (str/includes? s "[\"a\"] = 1"))
2705+
(is (str/includes? s "[\"b\"] = 2")))
2706+
(let [s (jss! '(let [x {}]
2707+
(assoc (assoc! x :a 1) :b 2)))]
2708+
(is (not (str/includes? s "assoc")))
2709+
(testing "assoc! mutates and potentially returns shared object"
2710+
(is (str/includes? s "...")))
2711+
(is (str/includes? s "[\"a\"] = 1"))
2712+
(is (not (str/includes? s "[\"b\"] = 2"))))))))
26842713

26852714
(defn init []
26862715
(t/run-tests 'squint.compiler-test 'squint.jsx-test 'squint.string-test 'squint.html-test))

0 commit comments

Comments
 (0)