Skip to content

Commit 973687b

Browse files
thhellerswannodette
authored andcommitted
CLJS-3077: Avoid generating unnecessary functions
Only need to capture locals when creating functions inside a loop or recur'ing fn.
1 parent fce78f1 commit 973687b

File tree

3 files changed

+68
-12
lines changed

3 files changed

+68
-12
lines changed

src/main/clojure/cljs/analyzer.cljc

+14-7
Original file line numberDiff line numberDiff line change
@@ -2126,12 +2126,15 @@
21262126
type (::type form-meta)
21272127
proto-impl (::protocol-impl form-meta)
21282128
proto-inline (::protocol-inline form-meta)
2129-
menv (if (> (count meths) 1)
2130-
(assoc env :context :expr)
2131-
env)
2132-
menv (merge menv
2133-
{:protocol-impl proto-impl
2134-
:protocol-inline proto-inline})
2129+
menv (-> env
2130+
(cond->
2131+
(> (count meths) 1)
2132+
(assoc :context :expr))
2133+
;; clear loop flag since method bodies won't be in a loop at first
2134+
;; only tracking this to keep track of locals we need to capture
2135+
(dissoc :in-loop)
2136+
(merge {:protocol-impl proto-impl
2137+
:protocol-inline proto-inline}))
21352138
methods (map #(disallowing-ns* (analyze-fn-method menv locals % type (nil? name))) meths)
21362139
mfa (transduce (map :fixed-arity) max 0 methods)
21372140
variadic (boolean (some :variadic? methods))
@@ -2166,6 +2169,7 @@
21662169
:tag 'function
21672170
:inferred-ret-tag inferred-ret-tag
21682171
:recur-frames *recur-frames*
2172+
:in-loop (:in-loop env)
21692173
:loop-lets *loop-lets*
21702174
:jsdoc [js-doc]
21712175
:max-fixed-arity mfa
@@ -2343,7 +2347,10 @@
23432347
(partition 2 bindings)
23442348
widened-tags))
23452349
bindings)
2346-
[bes env] (analyze-let-bindings encl-env bindings op)
2350+
[bes env] (-> encl-env
2351+
(cond->
2352+
(true? is-loop) (assoc :in-loop true))
2353+
(analyze-let-bindings bindings op))
23472354
recur-frame (when (true? is-loop)
23482355
{:params bes
23492356
:flag (atom nil)

src/main/clojure/cljs/compiler.cljc

+9-5
Original file line numberDiff line numberDiff line change
@@ -957,13 +957,17 @@
957957
(emitln "})()"))))
958958

959959
(defmethod emit* :fn
960-
[{variadic :variadic? :keys [name env methods max-fixed-arity recur-frames loop-lets]}]
960+
[{variadic :variadic? :keys [name env methods max-fixed-arity recur-frames in-loop loop-lets]}]
961961
;;fn statements get erased, serve no purpose and can pollute scope if named
962962
(when-not (= :statement (:context env))
963-
(let [loop-locals (->> (concat (mapcat :params (filter #(and % @(:flag %)) recur-frames))
964-
(mapcat :params loop-lets))
965-
(map munge)
966-
seq)]
963+
(let [recur-params (mapcat :params (filter #(and % @(:flag %)) recur-frames))
964+
loop-locals
965+
(->> (concat recur-params
966+
;; need to capture locals only if in recur fn or loop
967+
(when (or in-loop (seq recur-params))
968+
(mapcat :params loop-lets)))
969+
(map munge)
970+
seq)]
967971
(when loop-locals
968972
(when (= :return (:context env))
969973
(emits "return "))

src/test/clojure/cljs/compiler_tests.clj

+45
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,51 @@
286286
(is (str/includes? content "cljs.invoke_test.foo_record.foo_field_a;")))))
287287
#_(test-vars [#'test-optimized-invoke-emit])
288288

289+
(deftest test-cljs-3077
290+
(let [opts {}
291+
cenv (env/default-compiler-env opts)
292+
293+
test-compile
294+
(fn [code]
295+
(env/with-compiler-env cenv
296+
(with-out-str
297+
(emit
298+
(comp/with-core-cljs
299+
opts
300+
(fn [] (analyze aenv code nil opts)))))))
301+
302+
snippet1
303+
(test-compile
304+
'(defn wrapper1 [foo]
305+
(let [x 1]
306+
(prn (fn inner [] foo))
307+
(recur (inc foo)))))
308+
309+
snippet2
310+
(test-compile
311+
'(defn wrapper2 [foo]
312+
(loop [x 1]
313+
(prn (fn inner [] x))
314+
(recur (inc x))
315+
)))
316+
317+
snippet3
318+
(test-compile
319+
'(defn no-wrapper1 [foo]
320+
(let [x 1]
321+
(prn (fn inner [] foo)))))]
322+
323+
;; FIXME: not exactly a clean way to test if function wrappers are created or not
324+
;; captures foo,x
325+
(is (str/includes? snippet1 "(function (foo,x){"))
326+
;; captures x
327+
(is (str/includes? snippet2 "(function (x){"))
328+
;; no capture, no loop or recur
329+
(is (not (str/includes? snippet3 "(function (foo,x){")))
330+
(is (not (str/includes? snippet3 "(function (foo){")))
331+
(is (not (str/includes? snippet3 "(function (x){")))
332+
))
333+
289334
;; CLJS-1225
290335

291336
(comment

0 commit comments

Comments
 (0)