diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 27464f4f0..c87b75179 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -3110,7 +3110,7 @@ reduces them without incurring seq initialization" ([] "") ([x] (if (nil? x) "" - (.join #js [x] ""))) + (.toString x))) ([x & ys] (loop [sb (StringBuffer. (str x)) more ys] (if more diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index a58ec944f..1b7a1235b 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -866,23 +866,29 @@ (apply core/str))] (string-expr (list* 'js* (core/str "[" strs "].join('')") x ys))))) +(core/defn- compile-time-constant? [x] + (core/or + (core/string? x) + (core/keyword? x) + (core/boolean? x) + (core/number? x))) + ;; TODO: should probably be a compiler pass to avoid the code duplication (core/defmacro str - ([] "") - ([x] - (if (typed-expr? &env x '#{string}) - x - (string-expr (core/list 'js* "cljs.core.str.cljs$core$IFn$_invoke$arity$1(~{})" x)))) - ([x & ys] - (core/let [interpolate (core/fn [x] - (if (typed-expr? &env x '#{string clj-nil}) - "~{}" - "cljs.core.str.cljs$core$IFn$_invoke$arity$1(~{})")) - strs (core/->> (core/list* x ys) - (map interpolate) - (interpose ",") - (apply core/str))] - (string-expr (list* 'js* (core/str "[" strs "].join('')") x ys))))) + [& xs] + (core/let [interpolate (core/fn [x] + (core/cond + (typed-expr? &env x '#{clj-nil}) + nil + (compile-time-constant? x) + ["+~{}" x] + :else + ;; Note: can't assume non-nil despite tag here, so we go through str 1-arity + ["+cljs.core.str.cljs$core$IFn$_invoke$arity$1(~{})" x])) + strs+args (keep interpolate xs) + strs (string/join (map first strs+args)) + args (map second strs+args)] + (string-expr (list* 'js* (core/str "(\"\"" strs ")") args)))) (core/defn- bool-expr [e] (vary-meta e assoc :tag 'boolean)) diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index d92fb5105..5d887d313 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -1957,3 +1957,11 @@ (is (= "1two:threefour#{:five}[:six]#{:seven}{:eight :nine}" (apply cljs.core/str_ 1 ["two" :three 'four #{:five} [:six] #{:seven} {:eight :nine}]))) (is (= "1234" (apply cljs.core/str_ 1 2 [3 4])))) + +(deftest test-cljs-3452 + (let [obj #js {:valueOf (fn [] "dude") + :toString (fn [] "correct")} + str-fn (fn [x y] + (str x obj y "\"foobar\"" 1 :foo nil))] + (testing "object is stringified using toString" + (is (= "correct6\"foobar\"1:foo" (str-fn nil (+ 1 2 3))))))) diff --git a/src/test/cljs_build/cljs_3452_str_optimizations/core.cljs b/src/test/cljs_build/cljs_3452_str_optimizations/core.cljs new file mode 100644 index 000000000..75456c406 --- /dev/null +++ b/src/test/cljs_build/cljs_3452_str_optimizations/core.cljs @@ -0,0 +1,9 @@ +(ns cljs-3452-str-optimizations.core) + +(defn my-str-fn [x y] + (str x y nil ::foobar "my + +multiline + +string with `backticks`" + true false 3.14)) diff --git a/src/test/clojure/cljs/build_api_tests.clj b/src/test/clojure/cljs/build_api_tests.clj index 535f1c2c0..5c6ce522e 100644 --- a/src/test/clojure/cljs/build_api_tests.clj +++ b/src/test/clojure/cljs/build_api_tests.clj @@ -940,3 +940,21 @@ (.delete (io/file "package.json")) (test/delete-node-modules) (test/delete-out-files out)))) + +(deftest test-cljs-3452-str-optimizations + (testing "Test that uses compile time optimizations from str macro" + (let [out (.getPath (io/file (test/tmp-dir) "cljs-3452-str-optimizations-out"))] + (test/delete-out-files out) + (let [{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'cljs-3452-str-optimizations.core + :output-dir out + :optimizations :none + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env)] + (build/build (build/inputs (io/file inputs "cljs_3452_str_optimizations/core.cljs")) opts cenv)) + (let [source (slurp (io/file out "cljs_3452_str_optimizations/core.js"))] + (testing "only seven string concats, compile time nil is ignored" + (is (= 7 (count (re-seq #"[\+]" source))))) + (testing "only two 1-arity str calls, compile time constants are optimized" + (is (= 2 (count (re-seq #"\$1\(.*?\)" source)))))) + (test/delete-out-files out))))