From d26872c35229bc16c773008ebdc3f1dae177a3e5 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 2 Jun 2025 16:58:15 -0500 Subject: [PATCH 01/94] use release plugin --- script/build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/build b/script/build index 9ecd7f03e..e50a121f0 100755 --- a/script/build +++ b/script/build @@ -66,7 +66,7 @@ mv $AOT_CACHE_FILE src/main/cljs/cljs/core.cljs.cache.aot.edn # For Hudson server if [ "$HUDSON" = "true" ]; then - mvn -B -ntp --fail-at-end -DskipStaging=true -Psign $CLJS_SCRIPT_MVN_OPTS clean deploy + mvn -B -ntp --fail-at-end -DskipStaging=true -Psign $CLJS_SCRIPT_MVN_OPTS clean deploy release echo "Creating tag $TAG" git tag -f "$TAG" From 2e81eb3b1626e9021509a5ffd935b0091a389141 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 2 Jun 2025 17:03:14 -0500 Subject: [PATCH 02/94] Revert "use release plugin" This reverts commit d26872c35229bc16c773008ebdc3f1dae177a3e5. --- script/build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/build b/script/build index e50a121f0..9ecd7f03e 100755 --- a/script/build +++ b/script/build @@ -66,7 +66,7 @@ mv $AOT_CACHE_FILE src/main/cljs/cljs/core.cljs.cache.aot.edn # For Hudson server if [ "$HUDSON" = "true" ]; then - mvn -B -ntp --fail-at-end -DskipStaging=true -Psign $CLJS_SCRIPT_MVN_OPTS clean deploy release + mvn -B -ntp --fail-at-end -DskipStaging=true -Psign $CLJS_SCRIPT_MVN_OPTS clean deploy echo "Creating tag $TAG" git tag -f "$TAG" From 4d13556023452ec85d23f13d1f25798e07d6931c Mon Sep 17 00:00:00 2001 From: David Nolen Date: Thu, 5 Jun 2025 20:26:47 -0400 Subject: [PATCH 03/94] Fix protocol fn DCE issue (#252) * fixes invoke to cljs.core/str in protocol fns * add another trivial dce test w/ a protocol fn --- src/main/clojure/cljs/core.cljc | 4 ++-- src/test/cljs_build/trivial/core2.cljs | 3 +++ src/test/clojure/cljs/build_api_tests.clj | 13 +++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/test/cljs_build/trivial/core2.cljs diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 72f465427..8418c5eca 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -3287,9 +3287,9 @@ argseq#)))) (if (:macro meta) `(throw (js/Error. - (str "Invalid arity: " (- (alength (js-arguments)) 2)))) + (.join (array "Invalid arity: " (- (alength (js-arguments)) 2)) ""))) `(throw (js/Error. - (str "Invalid arity: " (alength (js-arguments)))))))))) + (.join (array "Invalid arity: " (alength (js-arguments))) "")))))))) ~@(map #(fn-method name %) fdecl) ;; optimization properties (set! (. ~name ~'-cljs$lang$maxFixedArity) ~maxfa) diff --git a/src/test/cljs_build/trivial/core2.cljs b/src/test/cljs_build/trivial/core2.cljs new file mode 100644 index 000000000..5e2f4fb0d --- /dev/null +++ b/src/test/cljs_build/trivial/core2.cljs @@ -0,0 +1,3 @@ +(ns trivial.core2) + +(. js/console (-lookup 1 2)) diff --git a/src/test/clojure/cljs/build_api_tests.clj b/src/test/clojure/cljs/build_api_tests.clj index e788c1ace..f65c1580f 100644 --- a/src/test/clojure/cljs/build_api_tests.clj +++ b/src/test/clojure/cljs/build_api_tests.clj @@ -720,6 +720,19 @@ (build/build (build/inputs (io/file inputs "trivial/core.cljs")) opts cenv) (is (< (.length out-file) 10000)))) +(deftest trivial-output-size-protocol + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-protocol-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core2 + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core2.cljs")) opts cenv) + (is (< (.length out-file) 10000)))) + (deftest cljs-3255-nil-inputs-build (let [out (.getPath (io/file (test/tmp-dir) "3255-test-out")) out-file (io/file out "main.js") From 0bf4c3ff9eb4b2a8af8294f6b7314c756fd32f4a Mon Sep 17 00:00:00 2001 From: David Nolen Date: Fri, 6 Jun 2025 16:53:54 -0400 Subject: [PATCH 04/94] More DCE cleanup (#253) Tiny DCE improvements - just use empty list in IndexedSeq - just invoke toString on StringBuffer - inline toString impl for EmptyList - reify should not emit basis static method - reify should set meta to nil if no actual meta, not empty map --- src/main/cljs/cljs/core.cljs | 7 +++---- src/main/clojure/cljs/core.cljc | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 3e789b6dc..79a8fe96d 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -909,7 +909,7 @@ writer (StringBufferWriter. sb)] (-pr-writer obj writer (pr-opts)) (-flush writer) - (str sb))) + (.toString sb))) ;;;;;;;;;;;;;;;;;;; Murmur3 ;;;;;;;;;;;;;;; @@ -1648,7 +1648,7 @@ reduces them without incurring seq initialization" (-first [_] (aget arr i)) (-rest [_] (if (< (inc i) (alength arr)) (IndexedSeq. arr (inc i) nil) - (list))) + ())) INext (-next [_] (if (< (inc i) (alength arr)) @@ -3206,8 +3206,7 @@ reduces them without incurring seq initialization" (deftype EmptyList [meta] Object - (toString [coll] - (pr-str* coll)) + (toString [coll] "()") (equiv [this other] (-equiv this other)) (indexOf [coll x] diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 8418c5eca..1424674a2 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -1365,7 +1365,7 @@ [& impls] (core/let [t (with-meta (gensym - (core/str "t_" + (core/str "t_reify_" (string/replace (core/str (munge ana/*cljs-ns*)) "." "$"))) {:anonymous true}) meta-sym (gensym "meta") @@ -1382,7 +1382,11 @@ IMeta (~'-meta [~this-sym] ~meta-sym) ~@impls)) - (new ~t ~@locals ~(ana/elide-reader-meta (meta &form)))))) + (new ~t ~@locals + ;; if the form meta is empty, emit nil + ~(core/let [form-meta (ana/elide-reader-meta (meta &form))] + (core/when-not (empty? form-meta) + form-meta)))))) (core/defmacro specify! "Identical to reify but mutates its first argument." @@ -1789,17 +1793,22 @@ [t fields & impls] (validate-fields "deftype" t fields) (core/let [env &env - r (:name (cljs.analyzer/resolve-var (dissoc env :locals) t)) + v (cljs.analyzer/resolve-var (dissoc env :locals) t) + r (:name v) [fpps pmasks] (prepare-protocol-masks env impls) protocols (collect-protocols impls env) t (vary-meta t assoc :protocols protocols - :skip-protocol-flag fpps) ] + :skip-protocol-flag fpps)] `(do (deftype* ~t ~fields ~pmasks ~(if (seq impls) `(extend-type ~t ~@(dt->et t impls fields)))) - (set! (.-getBasis ~t) (fn [] '[~@fields])) + ;; don't emit static basis method w/ reify + ;; nor for core types + ~@(core/when-not (core/or (string/starts-with? (name t) "t_reify") + (= 'cljs.core (:ns v))) + [`(set! (.-getBasis ~t) (fn [] '[~@fields]))]) (set! (.-cljs$lang$type ~t) true) (set! (.-cljs$lang$ctorStr ~t) ~(core/str r)) (set! (.-cljs$lang$ctorPrWriter ~t) (fn [this# writer# opt#] (-write writer# ~(core/str r)))) From e34ba40399add1bee917f7073687cfdcf4262019 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 9 Jun 2025 09:00:00 -0400 Subject: [PATCH 05/94] pr-writer-impl lower level impl for js object printing * remove pr-writer-impl dependence on lazy seq, MapEntry - use Array.map instead of map - reify IMapEntry instead of concrete MapEntry * use primitive regex method --- src/main/cljs/cljs/core.cljs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 79a8fe96d..f6a8d24ea 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -10529,9 +10529,15 @@ reduces them without incurring seq initialization" (do (-write writer "#js ") (print-map - (map (fn [k] - (MapEntry. (cond-> k (some? (re-matches #"[A-Za-z_\*\+\?!\-'][\w\*\+\?!\-']*" k)) keyword) (unchecked-get obj k) nil)) - (js-keys obj)) + (.map + (js-keys obj) + (fn [k] + (reify + IMapEntry + (-key [_] + (cond-> k (some? (.match k #"^[A-Za-z_\*\+\?!\-'][\w\*\+\?!\-']*$")) keyword)) + (-val [_] + (unchecked-get obj k))))) pr-writer writer opts)) (array? obj) From ffacd2314221e262c9460506a9688d3103041804 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 9 Jun 2025 12:03:18 -0400 Subject: [PATCH 06/94] Remove pr-opts calls, backwards compatibility tweaks (#257) * remove calls to pr-opts - just use dynamic binding - keep it backwards compatible * add long missing infer-tag case for :try --- src/main/cljs/cljs/core.cljs | 61 +++++++++++++++++++++-------- src/main/clojure/cljs/analyzer.cljc | 1 + 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index f6a8d24ea..f67cb27a1 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -267,6 +267,31 @@ "Returns true if x is not nil, false otherwise." [x] (not (nil? x))) +(defn- pr-opts-fnl [opts] + (if-not (nil? opts) + (:flush-on-newline opts) + *flush-on-newline*)) + +(defn- pr-opts-readably [opts] + (if-not (nil? opts) + (:readably opts) + *print-readably*)) + +(defn- pr-opts-meta [opts] + (if-not (nil? opts) + (:meta opts) + *print-meta*)) + +(defn- pr-opts-dup [opts] + (if-not (nil? opts) + (:dup opts) + *print-dup*)) + +(defn- pr-opts-len [opts] + (if-not (nil? opts) + (:print-length opts) + *print-length*)) + (defn object? "Returns true if x's constructor is Object" [x] @@ -907,7 +932,7 @@ [^not-native obj] (let [sb (StringBuffer.) writer (StringBufferWriter. sb)] - (-pr-writer obj writer (pr-opts)) + (-pr-writer obj writer nil) (-flush writer) (.toString sb))) @@ -10441,13 +10466,13 @@ reduces them without incurring seq initialization" (-write writer "#") (do (-write writer begin) - (if (zero? (:print-length opts)) + (if (zero? (pr-opts-len opts)) (when (seq coll) (-write writer (or (:more-marker opts) "..."))) (do (when (seq coll) (print-one (first coll) writer opts)) - (loop [coll (next coll) n (dec (:print-length opts))] + (loop [coll (next coll) n (dec (pr-opts-len opts))] (if (and coll (or (nil? n) (not (zero? n)))) (do (-write writer sep) @@ -10491,7 +10516,7 @@ reduces them without incurring seq initialization" (declare print-map) (defn print-meta? [opts obj] - (and (boolean (get opts :meta)) + (and (boolean (pr-opts-meta opts)) (implements? IMeta obj) (not (nil? (meta obj))))) @@ -10544,7 +10569,7 @@ reduces them without incurring seq initialization" (pr-sequential-writer writer pr-writer "#js [" " " "]" opts obj) (string? obj) - (if (:readably opts) + (if (pr-opts-readably opts) (-write writer (quote-string obj)) (-write writer obj)) @@ -10643,18 +10668,18 @@ reduces them without incurring seq initialization" ([] (newline nil)) ([opts] (string-print "\n") - (when (get opts :flush-on-newline) + (when (pr-opts-fnl opts) (flush)))) (defn pr-str "pr to a string, returning it. Fundamental entrypoint to IPrintWithWriter." [& objs] - (pr-str-with-opts objs (pr-opts))) + (pr-str-with-opts objs nil)) (defn prn-str "Same as pr-str followed by (newline)" [& objs] - (prn-str-with-opts objs (pr-opts))) + (prn-str-with-opts objs nil)) (defn pr "Prints the object(s) using string-print. Prints the @@ -10662,38 +10687,42 @@ reduces them without incurring seq initialization" By default, pr and prn print in a way that objects can be read by the reader" [& objs] - (pr-with-opts objs (pr-opts))) + (pr-with-opts objs nil)) (def ^{:doc "Prints the object(s) using string-print. print and println produce output for human consumption."} print (fn cljs-core-print [& objs] - (pr-with-opts objs (assoc (pr-opts) :readably false)))) + (binding [*print-readably* false] + (pr-with-opts objs nil)))) (defn print-str "print to a string, returning it" [& objs] - (pr-str-with-opts objs (assoc (pr-opts) :readably false))) + (binding [*print-readably* false] + (pr-str-with-opts objs nil))) (defn println "Same as print followed by (newline)" [& objs] - (pr-with-opts objs (assoc (pr-opts) :readably false)) + (binding [*print-readably* false] + (pr-with-opts objs nil)) (when *print-newline* - (newline (pr-opts)))) + (newline nil))) (defn println-str "println to a string, returning it" [& objs] - (prn-str-with-opts objs (assoc (pr-opts) :readably false))) + (binding [*print-readably* false] + (prn-str-with-opts objs nil))) (defn prn "Same as pr followed by (newline)." [& objs] - (pr-with-opts objs (pr-opts)) + (pr-with-opts objs nil) (when *print-newline* - (newline (pr-opts)))) + (newline nil))) (defn- strip-ns [named] diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 8c61c4586..a13c08545 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1568,6 +1568,7 @@ :throw impl/IGNORE_SYM :let (infer-tag env (:body ast)) :loop (infer-tag env (:body ast)) + :try (infer-tag env (:body ast)) :do (infer-tag env (:ret ast)) :fn-method (infer-tag env (:body ast)) :def (infer-tag env (:init ast)) From e611bd0b0b1afbc6cf45ed13b374599cf762f6eb Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 9 Jun 2025 17:02:08 -0400 Subject: [PATCH 07/94] avoid call to assoc, use -assoc --- src/main/cljs/cljs/core.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index f67cb27a1..5c94309c4 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -10624,7 +10624,7 @@ reduces them without incurring seq initialization" to a StringBuffer." [obj writer opts] (if-let [alt-impl (:alt-impl opts)] - (alt-impl obj writer (assoc opts :fallback-impl pr-writer-impl)) + (alt-impl obj writer (-assoc opts :fallback-impl pr-writer-impl)) (pr-writer-impl obj writer opts))) (defn pr-seq-writer [objs writer opts] From 078d59df2095c9a3f60b216e8d478c4614b55597 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 9 Jun 2025 23:38:54 -0400 Subject: [PATCH 08/94] Use primitives in print-map (#258) * lower print-map - lift pr-map-entry-helper, implement ISeqable - lift-ns uses array of MapEntry instead of actual map --- src/main/cljs/cljs/core.cljs | 50 +++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 5c94309c4..12025eaac 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -10520,6 +10520,14 @@ reduces them without incurring seq initialization" (implements? IMeta obj) (not (nil? (meta obj))))) +(defn- pr-map-entry [k v] + (reify + IMapEntry + (-key [_] k) + (-val [_] v) + ISeqable + (-seq [_] (IndexedSeq. #js [k v] 0 nil)))) + (defn- pr-writer-impl [obj writer opts] (cond @@ -10557,12 +10565,9 @@ reduces them without incurring seq initialization" (.map (js-keys obj) (fn [k] - (reify - IMapEntry - (-key [_] - (cond-> k (some? (.match k #"^[A-Za-z_\*\+\?!\-'][\w\*\+\?!\-']*$")) keyword)) - (-val [_] - (unchecked-get obj k))))) + (pr-map-entry + (cond-> k (some? (.match k #"^[A-Za-z_\*\+\?!\-'][\w\*\+\?!\-']*$")) keyword) + (unchecked-get obj k)))) pr-writer writer opts)) (array? obj) @@ -10731,20 +10736,22 @@ reduces them without incurring seq initialization" (keyword nil (name named)))) (defn- lift-ns - "Returns [lifted-ns lifted-map] or nil if m can't be lifted." + "Returns #js [lifted-ns lifted-map] or nil if m can't be lifted." [m] (when *print-namespace-maps* - (loop [ns nil - [[k v :as entry] & entries] (seq m) - lm (empty m)] - (if entry - (when (or (keyword? k) (symbol? k)) - (if ns - (when (= ns (namespace k)) - (recur ns entries (assoc lm (strip-ns k) v))) - (when-let [new-ns (namespace k)] - (recur new-ns entries (assoc lm (strip-ns k) v))))) - [ns lm])))) + (let [lm #js []] + (loop [ns nil + [[k v :as entry] & entries] (seq m)] + (if entry + (when (or (keyword? k) (symbol? k)) + (if ns + (when (= ns (namespace k)) + (.push lm (pr-map-entry (strip-ns k) v)) + (recur ns entries)) + (when-let [new-ns (namespace k)] + (.push lm (pr-map-entry (strip-ns k) v)) + (recur new-ns entries)))) + #js [ns lm]))))) (defn print-prefix-map [prefix m print-one writer opts] (pr-sequential-writer @@ -10757,10 +10764,11 @@ reduces them without incurring seq initialization" opts (seq m))) (defn print-map [m print-one writer opts] - (let [[ns lift-map] (when (map? m) - (lift-ns m))] + (let [ns&lift-map (when (map? m) + (lift-ns m)) + ns (some-> ns&lift-map (aget 0))] (if ns - (print-prefix-map (str "#:" ns) lift-map print-one writer opts) + (print-prefix-map (str "#:" ns) (aget ns&lift-map 1) print-one writer opts) (print-prefix-map nil m print-one writer opts)))) (extend-protocol IPrintWithWriter From 9bb394258ac7755833fdaced57ac6be5ed9d7fa5 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Tue, 10 Jun 2025 08:47:24 -0400 Subject: [PATCH 09/94] Remove the circularity that str has with IndexedSeq (#255) * remove the circularity that str has with IndexedSeq - add private str_ for cljs.core usage - keep str for now to avoid any potential breakage - add some simple tests for apply + str_ - the other cases already well covered by existing tests around printing --- src/main/cljs/cljs/core.cljs | 203 +++++++++++++++++------------- src/main/clojure/cljs/core.cljc | 18 +++ src/test/cljs/cljs/core_test.cljs | 9 ++ 3 files changed, 140 insertions(+), 90 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 12025eaac..b97f00fa2 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -357,7 +357,7 @@ (defn type->str [ty] (if-let [s (.-cljs$lang$ctorStr ty)] s - (str ty))) + (str_ ty))) ;; INTERNAL - do not use, only for Node.js (defn load-file [file] @@ -1175,7 +1175,7 @@ :else (throw (new js/Error "no conversion to symbol")))) ([ns name] (let [sym-str (if-not (nil? ns) - (str ns "/" name) + (str_ ns "/" name) name)] (Symbol. ns name sym-str nil nil)))) @@ -1184,7 +1184,7 @@ (isMacro [_] (. (val) -cljs$lang$macro)) (toString [_] - (str "#'" sym)) + (str_ "#'" sym)) IDeref (-deref [_] (val)) IMeta @@ -1299,7 +1299,7 @@ (native-satisfies? ISeqable coll) (-seq coll) - :else (throw (js/Error. (str coll " is not ISeqable")))))) + :else (throw (js/Error. (str_ coll " is not ISeqable")))))) (defn first "Returns the first item in the collection. Calls seq on its @@ -1448,7 +1448,7 @@ (-compare [this other] (if (instance? js/Date other) (garray/defaultCompare (.valueOf this) (.valueOf other)) - (throw (js/Error. (str "Cannot compare " this " to " other)))))) + (throw (js/Error. (str_ "Cannot compare " this " to " other)))))) (defprotocol Inst (inst-ms* [inst])) @@ -1967,7 +1967,7 @@ reduces them without incurring seq initialization" (-nth coll n) :else - (throw (js/Error. (str "nth not supported on this type " + (throw (js/Error. (str_ "nth not supported on this type " (type->str (type coll))))))) ([coll n not-found] (cond @@ -2000,7 +2000,7 @@ reduces them without incurring seq initialization" (-nth coll n not-found) :else - (throw (js/Error. (str "nth not supported on this type " + (throw (js/Error. (str_ "nth not supported on this type " (type->str (type coll)))))))) (defn nthrest @@ -2495,7 +2495,7 @@ reduces them without incurring seq initialization" (number? x) (if (number? y) (garray/defaultCompare x y) - (throw (js/Error. (str "Cannot compare " x " to " y)))) + (throw (js/Error. (str_ "Cannot compare " x " to " y)))) (satisfies? IComparable x) (-compare x y) @@ -2504,7 +2504,7 @@ reduces them without incurring seq initialization" (if (and (or (string? x) (array? x) (true? x) (false? x)) (identical? (type x) (type y))) (garray/defaultCompare x y) - (throw (js/Error. (str "Cannot compare " x " to " y)))))) + (throw (js/Error. (str_ "Cannot compare " x " to " y)))))) (defn ^:private compare-indexed "Compare indexed collection." @@ -3072,6 +3072,29 @@ reduces them without incurring seq initialization" ;;;;;;;;;;;;;;;;;;;;;;;;;; basics ;;;;;;;;;;;;;;;;;; +(defn- str_ + "Implementation detail. Internal str without circularity on IndexedSeq. + @param x + @param {...*} var_args" + [x var-args] + (cond + ;; works whether x is undefined or null (cljs nil) + (nil? x) "" + ;; if we have no more parameters, return + (undefined? var-args) (.join #js [x] "") + ;; var arg case without relying on CLJS fn machinery which creates + ;; a circularity via IndexedSeq + :else + (let [sb (StringBuffer.) + args (js-arguments) + len (alength args)] + (loop [i 0] + (if (< i len) + (do + (.append sb (cljs.core/str_ (aget args i))) + (recur (inc i))) + (.toString sb)))))) + (defn str "With no args, returns the empty string. With one arg x, returns x.toString(). (str nil) returns the empty string. With more than @@ -3081,10 +3104,10 @@ reduces them without incurring seq initialization" "" (.join #js [x] ""))) ([x & ys] - (loop [sb (StringBuffer. (str x)) more ys] - (if more - (recur (. sb (append (str (first more)))) (next more)) - (.toString sb))))) + (loop [sb (StringBuffer. (str x)) more ys] + (if more + (recur (. sb (append (str (first more)))) (next more)) + (.toString sb))))) (defn subs "Returns the substring of s beginning at start inclusive, and ending @@ -3419,7 +3442,7 @@ reduces them without incurring seq initialization" (deftype Keyword [ns name fqn ^:mutable _hash] Object - (toString [_] (str ":" fqn)) + (toString [_] (str_ ":" fqn)) (equiv [this other] (-equiv this other)) @@ -3443,7 +3466,7 @@ reduces them without incurring seq initialization" (-namespace [_] ns) IPrintWithWriter - (-pr-writer [o writer _] (-write writer (str ":" fqn)))) + (-pr-writer [o writer _] (-write writer (str_ ":" fqn)))) (defn keyword? "Return true if x is a Keyword" @@ -3473,7 +3496,7 @@ reduces them without incurring seq initialization" [x] (if (implements? INamed x) (-namespace x) - (throw (js/Error. (str "Doesn't support namespace: " x))))) + (throw (js/Error. (str_ "Doesn't support namespace: " x))))) (defn ident? "Return true if x is a symbol or keyword" @@ -3525,7 +3548,7 @@ reduces them without incurring seq initialization" (keyword? name) (cljs.core/name name) (symbol? name) (cljs.core/name name) :else name)] - (Keyword. ns name (str (when ns (str ns "/")) name) nil)))) + (Keyword. ns name (str_ (when ns (str_ ns "/")) name) nil)))) (deftype LazySeq [meta ^:mutable fn ^:mutable s ^:mutable __hash] Object @@ -4187,7 +4210,7 @@ reduces them without incurring seq initialization" (string? coll) (string-iter coll) (array? coll) (array-iter coll) (seqable? coll) (seq-iter coll) - :else (throw (js/Error. (str "Cannot create iterator from " coll))))) + :else (throw (js/Error. (str_ "Cannot create iterator from " coll))))) (deftype Many [vals] Object @@ -4199,7 +4222,7 @@ reduces them without incurring seq initialization" (isEmpty [this] (zero? (.-length vals))) (toString [this] - (str "Many: " vals))) + (str_ "Many: " vals))) (def ^:private NONE #js {}) @@ -4213,21 +4236,21 @@ reduces them without incurring seq initialization" (Many. #js [val o]))) (remove [this] (if (identical? val NONE) - (throw (js/Error. (str "Removing object from empty buffer"))) + (throw (js/Error. (str_ "Removing object from empty buffer"))) (let [ret val] (set! val NONE) ret))) (isEmpty [this] (identical? val NONE)) (toString [this] - (str "Single: " val))) + (str_ "Single: " val))) (deftype Empty [] Object (add [this o] (Single. o)) (remove [this] - (throw (js/Error. (str "Removing object from empty buffer")))) + (throw (js/Error. (str_ "Removing object from empty buffer")))) (isEmpty [this] true) (toString [this] @@ -4374,8 +4397,8 @@ reduces them without incurring seq initialization" (defn even? "Returns true if n is even, throws an exception if n is not an integer" [n] (if (integer? n) - (zero? (bit-and n 1)) - (throw (js/Error. (str "Argument must be an integer: " n))))) + (zero? (bit-and n 1)) + (throw (js/Error. (str_ "Argument must be an integer: " n))))) (defn odd? "Returns true if n is odd, throws an exception if n is not an integer" @@ -5549,7 +5572,7 @@ reduces them without incurring seq initialization" ret)))))) (defn- vector-index-out-of-bounds [i cnt] - (throw (js/Error. (str "No item " i " in vector of length " cnt)))) + (throw (js/Error. (str_ "No item " i " in vector of length " cnt)))) (defn- first-array-for-longvec [pv] ;; invariants: (count pv) > 32. @@ -5778,14 +5801,14 @@ reduces them without incurring seq initialization" IVector (-assoc-n [coll n val] (cond - (and (<= 0 n) (< n cnt)) - (if (<= (tail-off coll) n) + (and (<= 0 n) (< n cnt)) + (if (<= (tail-off coll) n) (let [new-tail (aclone tail)] (aset new-tail (bit-and n 0x01f) val) (PersistentVector. meta cnt shift root new-tail nil)) (PersistentVector. meta cnt shift (do-assoc coll shift root n val) tail nil)) - (== n cnt) (-conj coll val) - :else (throw (js/Error. (str "Index " n " out of bounds [0," cnt "]"))))) + (== n cnt) (-conj coll val) + :else (throw (js/Error. (str_ "Index " n " out of bounds [0," cnt "]"))))) IReduce (-reduce [v f] @@ -6104,7 +6127,7 @@ reduces them without incurring seq initialization" (-assoc-n [coll n val] (let [v-pos (+ start n)] (if (or (neg? n) (<= (inc end) v-pos)) - (throw (js/Error. (str "Index " n " out of bounds [0," (-count coll) "]"))) + (throw (js/Error. (str_ "Index " n " out of bounds [0," (-count coll) "]"))) (build-subvec meta (assoc v v-pos val) start (max end (inc v-pos)) nil)))) IReduce @@ -6292,7 +6315,7 @@ reduces them without incurring seq initialization" :else (throw (js/Error. - (str "Index " n " out of bounds for TransientVector of length" cnt)))) + (str_ "Index " n " out of bounds for TransientVector of length" cnt)))) (throw (js/Error. "assoc! after persistent!")))) (-pop! [tcoll] @@ -7199,7 +7222,7 @@ reduces them without incurring seq initialization" idx (array-index-of ret k)] (if (== idx -1) (doto ret (.push k) (.push v)) - (throw (js/Error. (str "Duplicate key: " k))))) + (throw (js/Error. (str_ "Duplicate key: " k))))) (recur (+ i 2)))) (let [cnt (/ (alength arr) 2)] (PersistentArrayMap. nil cnt arr nil))))) @@ -8268,7 +8291,7 @@ reduces them without incurring seq initialization" (loop [i 0 ^not-native out (transient (.-EMPTY PersistentHashMap))] (if (< i len) (if (<= (alength vs) i) - (throw (js/Error. (str "No value supplied for key: " (aget ks i)))) + (throw (js/Error. (str_ "No value supplied for key: " (aget ks i)))) (recur (inc i) (-assoc! out (aget ks i) (aget vs i)))) (persistent! out)))))) @@ -8280,7 +8303,7 @@ reduces them without incurring seq initialization" (when (< i len) (-assoc! ret (aget arr i) (aget arr (inc i))) (if (not= (-count ret) (inc (/ i 2))) - (throw (js/Error. (str "Duplicate key: " (aget arr i)))) + (throw (js/Error. (str_ "Duplicate key: " (aget arr i)))) (recur (+ i 2))))) (-persistent! ret)))) @@ -9143,7 +9166,7 @@ reduces them without incurring seq initialization" (if in (let [in' (next in)] (if (nil? in') - (throw (js/Error. (str "No value supplied for key: " (first in)))) + (throw (js/Error. (str_ "No value supplied for key: " (first in)))) (recur (next in') (assoc! out (first in) (first in')) ))) (persistent! out)))) @@ -9155,7 +9178,7 @@ reduces them without incurring seq initialization" (.-arr keyvals) (into-array keyvals))] (if (odd? (alength arr)) - (throw (js/Error. (str "No value supplied for key: " (last arr)))) + (throw (js/Error. (str_ "No value supplied for key: " (last arr)))) (.createAsIfByAssoc PersistentArrayMap arr)))) (defn seq-to-map-for-destructuring @@ -9518,7 +9541,7 @@ reduces them without incurring seq initialization" (dotimes [i len] (-conj! t (aget items i)) (when-not (= (count t) (inc i)) - (throw (js/Error. (str "Duplicate key: " (aget items i)))))) + (throw (js/Error. (str_ "Duplicate key: " (aget items i)))))) (-persistent! t)))) (set! (.-createAsIfByAssoc PersistentHashSet) @@ -9767,7 +9790,7 @@ reduces them without incurring seq initialization" (-name x) (if (string? x) x - (throw (js/Error. (str "Doesn't support name: " x)))))) + (throw (js/Error. (str_ "Doesn't support name: " x)))))) (defn zipmap "Returns a map with the keys mapped to the corresponding vals." @@ -10508,7 +10531,7 @@ reduces them without incurring seq initialization" (defn ^:private quote-string [s] - (str \" + (str_ \" (.replace s (js/RegExp "[\\\\\"\b\f\n\r\t]" "g") (fn [match] (unchecked-get char-escapes match))) \")) @@ -10548,7 +10571,7 @@ reduces them without incurring seq initialization" (-pr-writer obj writer opts) (or (true? obj) (false? obj)) - (-write writer (str obj)) + (-write writer (str_ obj)) (number? obj) (-write writer @@ -10556,7 +10579,7 @@ reduces them without incurring seq initialization" ^boolean (js/isNaN obj) "##NaN" (identical? obj js/Number.POSITIVE_INFINITY) "##Inf" (identical? obj js/Number.NEGATIVE_INFINITY) "##-Inf" - :else (str obj))) + :else (str_ obj))) (object? obj) (do @@ -10585,15 +10608,15 @@ reduces them without incurring seq initialization" name)] (write-all writer "#object[" name (if *print-fn-bodies* - (str " \"" (str obj) "\"") + (str_ " \"" (str_ obj) "\"") "") "]")) (instance? js/Date obj) (let [normalize (fn [n len] - (loop [ns (str n)] + (loop [ns (str_ n)] (if (< (count ns) len) - (recur (str "0" ns)) + (recur (str_ "0" ns)) ns)))] (write-all writer "#inst \"" @@ -10621,7 +10644,7 @@ reduces them without incurring seq initialization" name)] (if (nil? (. obj -constructor)) (write-all writer "#object[" name "]") - (write-all writer "#object[" name " " (str obj) "]")))))))) + (write-all writer "#object[" name " " (str_ obj) "]")))))))) (defn- pr-writer "Prefer this to pr-seq, because it makes the printing function @@ -10651,7 +10674,7 @@ reduces them without incurring seq initialization" [objs opts] (if (empty? objs) "" - (str (pr-sb-with-opts objs opts)))) + (str_ (pr-sb-with-opts objs opts)))) (defn prn-str-with-opts "Same as pr-str-with-opts followed by (newline)" @@ -10660,7 +10683,7 @@ reduces them without incurring seq initialization" "\n" (let [sb (pr-sb-with-opts objs opts)] (.append sb \newline) - (str sb)))) + (str_ sb)))) (defn- pr-with-opts "Prints a sequence of objects using string-print, observing all @@ -10760,7 +10783,7 @@ reduces them without incurring seq initialization" (do (print-one (key e) w opts) (-write w \space) (print-one (val e) w opts))) - (str prefix "{") ", " "}" + (str_ prefix "{") ", " "}" opts (seq m))) (defn print-map [m print-one writer opts] @@ -10768,7 +10791,7 @@ reduces them without incurring seq initialization" (lift-ns m)) ns (some-> ns&lift-map (aget 0))] (if ns - (print-prefix-map (str "#:" ns) (aget ns&lift-map 1) print-one writer opts) + (print-prefix-map (str_ "#:" ns) (aget ns&lift-map 1) print-one writer opts) (print-prefix-map nil m print-one writer opts)))) (extend-protocol IPrintWithWriter @@ -10901,43 +10924,43 @@ reduces them without incurring seq initialization" (-compare [x y] (if (symbol? y) (compare-symbols x y) - (throw (js/Error. (str "Cannot compare " x " to " y))))) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) Keyword (-compare [x y] (if (keyword? y) (compare-keywords x y) - (throw (js/Error. (str "Cannot compare " x " to " y))))) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) Subvec (-compare [x y] (if (vector? y) (compare-indexed x y) - (throw (js/Error. (str "Cannot compare " x " to " y))))) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) PersistentVector (-compare [x y] (if (vector? y) (compare-indexed x y) - (throw (js/Error. (str "Cannot compare " x " to " y))))) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) MapEntry (-compare [x y] (if (vector? y) (compare-indexed x y) - (throw (js/Error. (str "Cannot compare " x " to " y))))) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) BlackNode (-compare [x y] (if (vector? y) (compare-indexed x y) - (throw (js/Error. (str "Cannot compare " x " to " y))))) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) RedNode (-compare [x y] (if (vector? y) (compare-indexed x y) - (throw (js/Error. (str "Cannot compare " x " to " y)))))) + (throw (js/Error. (str_ "Cannot compare " x " to " y)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reference Types ;;;;;;;;;;;;;;;; @@ -10998,7 +11021,7 @@ reduces them without incurring seq initialization" ([prefix-string] (when (nil? gensym_counter) (set! gensym_counter (atom 0))) - (symbol (str prefix-string (swap! gensym_counter inc))))) + (symbol (str_ prefix-string (swap! gensym_counter inc))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Delay ;;;;;;;;;;;;;;;;;;;; @@ -11228,7 +11251,7 @@ reduces them without incurring seq initialization" (nil? x) nil (satisfies? IEncodeJS x) (-clj->js x) (keyword? x) (keyword-fn x) - (symbol? x) (str x) + (symbol? x) (str_ x) (map? x) (let [m (js-obj)] (doseq [[k v] x] (gobject/set m (keyfn k) (thisfn v))) @@ -11252,7 +11275,7 @@ reduces them without incurring seq initialization" ([x] (js->clj x :keywordize-keys false)) ([x & opts] (let [{:keys [keywordize-keys]} opts - keyfn (if keywordize-keys keyword str) + keyfn (if keywordize-keys keyword str_) f (fn thisfn [x] (cond (satisfies? IEncodeClojure x) @@ -11427,9 +11450,9 @@ reduces them without incurring seq initialization" (or (when-not (contains? (tp tag) parent) (when (contains? (ta tag) parent) - (throw (js/Error. (str tag "already has" parent "as ancestor")))) + (throw (js/Error. (str_ tag "already has" parent "as ancestor")))) (when (contains? (ta parent) tag) - (throw (js/Error. (str "Cyclic derivation:" parent "has" tag "as ancestor")))) + (throw (js/Error. (str_ "Cyclic derivation:" parent "has" tag "as ancestor")))) {:parents (assoc (:parents h) tag (conj (get tp tag #{}) parent)) :ancestors (tf (:ancestors h) tag td parent ta) :descendants (tf (:descendants h) parent ta tag td)}) @@ -11492,7 +11515,7 @@ reduces them without incurring seq initialization" be)] (when-not (dominates (first be2) k prefer-table @hierarchy) (throw (js/Error. - (str "Multiple methods in multimethod '" name + (str_ "Multiple methods in multimethod '" name "' match dispatch value: " dispatch-val " -> " k " and " (first be2) ", and neither is preferred")))) be2) @@ -11523,7 +11546,7 @@ reduces them without incurring seq initialization" (-dispatch-fn [mf])) (defn- throw-no-method-error [name dispatch-val] - (throw (js/Error. (str "No method in multimethod '" name "' for dispatch value: " dispatch-val)))) + (throw (js/Error. (str_ "No method in multimethod '" name "' for dispatch value: " dispatch-val)))) (deftype MultiFn [name dispatch-fn default-dispatch-val hierarchy method-table prefer-table method-cache cached-hierarchy] @@ -11689,7 +11712,7 @@ reduces them without incurring seq initialization" (-prefer-method [mf dispatch-val-x dispatch-val-y] (when (prefers* dispatch-val-y dispatch-val-x prefer-table) - (throw (js/Error. (str "Preference conflict in multimethod '" name "': " dispatch-val-y + (throw (js/Error. (str_ "Preference conflict in multimethod '" name "': " dispatch-val-y " is already preferred to " dispatch-val-x)))) (swap! prefer-table (fn [old] @@ -11764,7 +11787,7 @@ reduces them without incurring seq initialization" IPrintWithWriter (-pr-writer [_ writer _] - (-write writer (str "#uuid \"" uuid "\""))) + (-write writer (str_ "#uuid \"" uuid "\""))) IHash (-hash [this] @@ -11776,7 +11799,7 @@ reduces them without incurring seq initialization" (-compare [this other] (if (instance? UUID other) (garray/defaultCompare uuid (.-uuid other)) - (throw (js/Error. (str "Cannot compare " this " to " other)))))) + (throw (js/Error. (str_ "Cannot compare " this " to " other)))))) (defn uuid "Returns a UUID consistent with the string s." @@ -11790,14 +11813,14 @@ reduces them without incurring seq initialization" (letfn [(^string quad-hex [] (let [unpadded-hex ^string (.toString (rand-int 65536) 16)] (case (count unpadded-hex) - 1 (str "000" unpadded-hex) - 2 (str "00" unpadded-hex) - 3 (str "0" unpadded-hex) + 1 (str_ "000" unpadded-hex) + 2 (str_ "00" unpadded-hex) + 3 (str_ "0" unpadded-hex) unpadded-hex)))] (let [ver-tripple-hex ^string (.toString (bit-or 0x4000 (bit-and 0x0fff (rand-int 65536))) 16) res-tripple-hex ^string (.toString (bit-or 0x8000 (bit-and 0x3fff (rand-int 65536))) 16)] (uuid - (str (quad-hex) (quad-hex) "-" (quad-hex) "-" + (str_ (quad-hex) (quad-hex) "-" (quad-hex) "-" ver-tripple-hex "-" res-tripple-hex "-" (quad-hex) (quad-hex) (quad-hex)))))) @@ -11971,7 +11994,7 @@ reduces them without incurring seq initialization" IPrintWithWriter (-pr-writer [o writer opts] - (-write writer (str "#" tag " ")) + (-write writer (str_ "#" tag " ")) (pr-writer form writer opts))) (defn tagged-literal? @@ -12024,11 +12047,11 @@ reduces them without incurring seq initialization" (if (seq ks) (recur (next ks) - (str + (str_ (cond-> ret - (not (identical? ret "")) (str "|")) + (not (identical? ret "")) (str_ "|")) (first ks))) - (str ret "|\\$")))))) + (str_ ret "|\\$")))))) DEMUNGE_PATTERN) (defn- ^string munge-str [name] @@ -12044,10 +12067,10 @@ reduces them without incurring seq initialization" (.toString sb))) (defn munge [name] - (let [name' (munge-str (str name)) + (let [name' (munge-str (str_ name)) name' (cond (identical? name' "..") "_DOT__DOT_" - (js-reserved? name') (str name' "$") + (js-reserved? name') (str_ name' "$") :else name')] (if (symbol? name) (symbol name') @@ -12062,17 +12085,17 @@ reduces them without incurring seq initialization" (if-let [match (.exec r munged-name)] (let [[x] match] (recur - (str ret + (str_ ret (.substring munged-name last-match-end (- (. r -lastIndex) (. x -length))) (if (identical? x "$") "/" (gobject/get DEMUNGE_MAP x))) (. r -lastIndex))) - (str ret + (str_ ret (.substring munged-name last-match-end (.-length munged-name))))))) (defn demunge [name] - ((if (symbol? name) symbol str) - (let [name' (str name)] + ((if (symbol? name) symbol str_) + (let [name' (str_ name)] (if (identical? name' "_DOT__DOT_") ".." (demunge-str name'))))) @@ -12151,14 +12174,14 @@ reduces them without incurring seq initialization" (deftype Namespace [obj name] Object (findInternedVar [this sym] - (let [k (munge (str sym))] + (let [k (munge (str_ sym))] (when ^boolean (gobject/containsKey obj k) - (let [var-sym (symbol (str name) (str sym)) + (let [var-sym (symbol (str_ name) (str_ sym)) var-meta {:ns this}] (Var. (ns-lookup obj k) var-sym var-meta))))) (getName [_] name) (toString [_] - (str name)) + (str_ name)) IEquiv (-equiv [_ other] (if (instance? Namespace other) @@ -12183,10 +12206,10 @@ reduces them without incurring seq initialization" (defn find-ns-obj "Bootstrap only." [ns] - (let [munged-ns (munge (str ns)) + (let [munged-ns (munge (str_ ns)) segs (.split munged-ns ".")] (case *target* - "nodejs" (if ^boolean js/COMPILED + "nodejs" (if ^boolean js/COMPILED ; Under simple optimizations on nodejs, namespaces will be in module ; rather than global scope and must be accessed by a direct call to eval. ; The first segment may refer to an undefined variable, so its evaluation @@ -12201,7 +12224,7 @@ reduces them without incurring seq initialization" (next segs)) (find-ns-obj* goog/global segs)) ("default" "webworker") (find-ns-obj* goog/global segs) - (throw (js/Error. (str "find-ns-obj not supported for target " *target*)))))) + (throw (js/Error. (str_ "find-ns-obj not supported for target " *target*)))))) (defn ns-interns* "Returns a map of the intern mappings for the namespace. @@ -12213,7 +12236,7 @@ reduces them without incurring seq initialization" (let [var-sym (symbol (demunge k))] (assoc ret var-sym (Var. #(gobject/get ns-obj k) - (symbol (str sym) (str var-sym)) {:ns ns}))))] + (symbol (str_ sym) (str_ var-sym)) {:ns ns}))))] (reduce step {} (js-keys ns-obj))))) (defn create-ns @@ -12244,9 +12267,9 @@ reduces them without incurring seq initialization" [ns] (when (nil? NS_CACHE) (set! NS_CACHE (atom {}))) - (let [ns-str (str ns) + (let [ns-str (str_ ns) ns (if (not ^boolean (gstring/contains ns-str "$macros")) - (symbol (str ns-str "$macros")) + (symbol (str_ ns-str "$macros")) ns) the-ns (get @NS_CACHE ns)] (if-not (nil? the-ns) @@ -12277,7 +12300,7 @@ reduces them without incurring seq initialization" (defn ^:private parsing-err "Construct message for parsing for non-string parsing error" [val] - (str "Expected string, got: " (if (nil? val) "nil" (goog/typeOf val)))) + (str_ "Expected string, got: " (if (nil? val) "nil" (goog/typeOf val)))) (defn ^number parse-long "Parse string of decimal digits with optional leading -/+ and return an diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 1424674a2..8393a1a67 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -849,6 +849,24 @@ (core/defn- string-expr [e] (vary-meta e assoc :tag 'string)) +(core/defmacro str_ + ([] "") + ([x] + (if (typed-expr? &env x '#{string}) + x + (string-expr (core/list 'js* "cljs.core.str_(~{})" x)))) + ([x & ys] + (core/let [interpolate (core/fn [x] + (if (typed-expr? &env x '#{string clj-nil}) + "~{}" + "cljs.core.str_(~{})")) + strs (core/->> (core/list* x ys) + (map interpolate) + (interpose ",") + (apply core/str))] + (string-expr (list* 'js* (core/str "[" strs "].join('')") x ys))))) + +;; TODO: should probably be a compiler pass to avoid the code duplication (core/defmacro str ([] "") ([x] diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index 9c4a62528..58720b5a1 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -2056,3 +2056,12 @@ [1 2 {:a 1, :b 2, :c 3}])) (is (= (test-keys :d 4 {:a 1, :b 2, :c 3}) [1 2 {:d 4, :a 1, :b 2, :c 3}])))) + +(deftest test-str_ + (is (= "" (apply cljs.core/str_ nil))) + (is (= "" (apply cljs.core/str_ []))) + (is (= "1" (apply cljs.core/str_ 1 []))) + (is (= "12" (apply cljs.core/str_ 1 [2]))) + (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])))) \ No newline at end of file From ad83b3edd3ef088ef63f9e3c05bf594824e569ab Mon Sep 17 00:00:00 2001 From: David Nolen Date: Tue, 10 Jun 2025 09:52:49 -0400 Subject: [PATCH 10/94] More trivial source build tests verifying DCE doesn't regress (#259) * add two more trivial output tests - keyword should be very small - vector by itself should be reasonable - bump windows version --- .github/workflows/test.yaml | 4 +-- src/test/cljs_build/trivial/core2.cljs | 2 +- src/test/cljs_build/trivial/core3.cljs | 3 +++ src/test/cljs_build/trivial/core4.cljs | 3 +++ src/test/clojure/cljs/build_api_tests.clj | 30 +++++++++++++++++++++-- 5 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 src/test/cljs_build/trivial/core3.cljs create mode 100644 src/test/cljs_build/trivial/core4.cljs diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e6a4590d0..e98aa8818 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -62,7 +62,7 @@ jobs: # Runtime Tests runtime-windows-test: name: Runtime Windows Tests - runs-on: windows-2019 + runs-on: windows-2022 steps: - uses: actions/checkout@v2 @@ -215,7 +215,7 @@ jobs: # Compiler Windows Tests compiler-windows-test: name: Compiler Windows Tests - runs-on: windows-2019 + runs-on: windows-2022 steps: - uses: actions/checkout@v2 diff --git a/src/test/cljs_build/trivial/core2.cljs b/src/test/cljs_build/trivial/core2.cljs index 5e2f4fb0d..a79e64e80 100644 --- a/src/test/cljs_build/trivial/core2.cljs +++ b/src/test/cljs_build/trivial/core2.cljs @@ -1,3 +1,3 @@ (ns trivial.core2) -(. js/console (-lookup 1 2)) +(.log js/console (-lookup 1 2)) diff --git a/src/test/cljs_build/trivial/core3.cljs b/src/test/cljs_build/trivial/core3.cljs new file mode 100644 index 000000000..a66db571c --- /dev/null +++ b/src/test/cljs_build/trivial/core3.cljs @@ -0,0 +1,3 @@ +(ns trivial.core3) + +(.log js/console :foo) diff --git a/src/test/cljs_build/trivial/core4.cljs b/src/test/cljs_build/trivial/core4.cljs new file mode 100644 index 000000000..f8f4c6d25 --- /dev/null +++ b/src/test/cljs_build/trivial/core4.cljs @@ -0,0 +1,3 @@ +(ns trivial.core4) + +(.log js/console []) diff --git a/src/test/clojure/cljs/build_api_tests.clj b/src/test/clojure/cljs/build_api_tests.clj index f65c1580f..f05a4ac3f 100644 --- a/src/test/clojure/cljs/build_api_tests.clj +++ b/src/test/clojure/cljs/build_api_tests.clj @@ -718,7 +718,7 @@ cenv (env/default-compiler-env)] (test/delete-out-files out) (build/build (build/inputs (io/file inputs "trivial/core.cljs")) opts cenv) - (is (< (.length out-file) 10000)))) + (is (< (.length out-file) 10240)))) (deftest trivial-output-size-protocol (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-protocol-test-out")) @@ -731,7 +731,33 @@ cenv (env/default-compiler-env)] (test/delete-out-files out) (build/build (build/inputs (io/file inputs "trivial/core2.cljs")) opts cenv) - (is (< (.length out-file) 10000)))) + (is (< (.length out-file) 10240)))) + +(deftest trivial-output-size-keyword + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-keyword-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core3 + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core3.cljs")) opts cenv) + (is (< (.length out-file) 10240)))) + +(deftest trivial-output-size-vector + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-vector-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core4 + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core4.cljs")) opts cenv) + (is (< (.length out-file) 32768)))) (deftest cljs-3255-nil-inputs-build (let [out (.getPath (io/file (test/tmp-dir) "3255-test-out")) From 90a40f69d5d3f2c34aa2c2e6612bb14296b2e383 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Fri, 13 Jun 2025 05:47:11 -0400 Subject: [PATCH 11/94] Add code size ratchet for PHM --- src/test/cljs_build/trivial/core5.cljs | 3 +++ src/test/clojure/cljs/build_api_tests.clj | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/test/cljs_build/trivial/core5.cljs diff --git a/src/test/cljs_build/trivial/core5.cljs b/src/test/cljs_build/trivial/core5.cljs new file mode 100644 index 000000000..1e7f87756 --- /dev/null +++ b/src/test/cljs_build/trivial/core5.cljs @@ -0,0 +1,3 @@ +(ns trivial.core5) + +(.log js/console {}) \ No newline at end of file diff --git a/src/test/clojure/cljs/build_api_tests.clj b/src/test/clojure/cljs/build_api_tests.clj index f05a4ac3f..a1a2f3871 100644 --- a/src/test/clojure/cljs/build_api_tests.clj +++ b/src/test/clojure/cljs/build_api_tests.clj @@ -759,6 +759,19 @@ (build/build (build/inputs (io/file inputs "trivial/core4.cljs")) opts cenv) (is (< (.length out-file) 32768)))) +(deftest trivial-output-size-map + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-map-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core5 + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core5.cljs")) opts cenv) + (is (< (.length out-file) 92160)))) + (deftest cljs-3255-nil-inputs-build (let [out (.getPath (io/file (test/tmp-dir) "3255-test-out")) out-file (io/file out "main.js") From 55491752ad2ec03f773692ddbd1ce97cac7e3722 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 6 Jul 2025 09:59:08 -0400 Subject: [PATCH 12/94] Refactor has-extern? / js-tag (#246) * fix up tests so they don't throw if no warnings * resolve-extern, which can be used by both has-extern? and js-tag, returns both resolved prefix and var info * remove various hacks around extern resolution (Number, Window, prototype etc.), resolve-extern handles everything * undefined is a ref cycle, special case * change normalize-js-tag so it marks the ctor prop * add normalize-unresolved-prefix to fix up the cases we can't find * add impl unit tests * more unit test - can finally resolve crypto.subtle, verify type inference as well * add lift-tag-to-js helper * in resolve-var add the ctor to the tag to track later, this also lets the extra information flow * in analyze-dot check to see if the target is a constructor - if it is use that as tag instead, Function is not useful * test assertion that we can figure out the return even if a extern js ctor is bound to a local * add compiler test case for inferring return of Number.isNaN * add non-ctor inference for array, string, boolean and number, will be useful later * add js/isNaN test * add isArray extern test * don't return raised js/Foo types for boolean, number, string * remove ^boolean hint from array? ass test * remove hint for make-array, add test * remove hints for isFinite and isSafeInteger, tests * can infer distinct?, add test * remove various ^boolean cases no longer needed * FIXME comments about dubious ^boolean cases * move ^boolean hint from special-symbol? to contains? where it belongs, test case * goog.object/containsKey type inference doesn't work for reason, leave a trail for later * goog.string/contains does work, add test * remove hint from NaN? * FIXME note about re-matches, another dubious case of ^boolean hints --- src/main/cljs/cljs/core.cljs | 30 ++-- src/main/clojure/cljs/analyzer.cljc | 150 +++++++++++----- src/main/clojure/cljs/compiler.cljc | 3 +- src/main/clojure/cljs/externs.clj | 17 +- src/test/clojure/cljs/compiler_tests.clj | 19 +- src/test/clojure/cljs/externs_infer_tests.clj | 164 ++++++++++++++++-- .../clojure/cljs/externs_parsing_tests.clj | 6 + .../clojure/cljs/type_inference_tests.clj | 40 +++++ 8 files changed, 350 insertions(+), 79 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index b97f00fa2..e1c7b067e 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -243,7 +243,7 @@ [x] (coercive-= x nil)) -(defn ^boolean array? +(defn array? "Returns true if x is a JavaScript array." [x] (if (identical? *target* "nodejs") @@ -444,7 +444,7 @@ (declare apply) -(defn ^array make-array +(defn make-array "Construct a JavaScript array of the specified dimensions. Accepts ignored type argument for compatibility with Clojure. Note that there is no efficient way to allocate multi-dimensional arrays in JavaScript; as such, this function @@ -1055,8 +1055,8 @@ (bit-xor (-hash o) 0) (number? o) - (if ^boolean (js/isFinite o) - (if-not ^boolean (.isSafeInteger js/Number o) + (if (js/isFinite o) + (if-not (.isSafeInteger js/Number o) (hash-double o) (js-mod (Math/floor o) 2147483647)) (case o @@ -2355,7 +2355,7 @@ reduces them without incurring seq initialization" "Returns true if n is a JavaScript number with no decimal part." [n] (and (number? n) - (not ^boolean (js/isNaN n)) + (not (js/isNaN n)) (not (identical? n js/Infinity)) (== (js/parseFloat n) (js/parseInt n 10)))) @@ -2432,7 +2432,7 @@ reduces them without incurring seq initialization" (or (identical? x js/Number.POSITIVE_INFINITY) (identical? x js/Number.NEGATIVE_INFINITY))) -(defn contains? +(defn ^boolean contains? "Returns true if key is present in the given collection, otherwise returns false. Note that for numerically indexed collections like vectors and arrays, this tests if the numeric key is within the @@ -2462,12 +2462,12 @@ reduces them without incurring seq initialization" (contains? coll k)) (MapEntry. k (get coll k) nil)))) -(defn ^boolean distinct? +(defn distinct? "Returns true if no two of the arguments are =" ([x] true) ([x y] (not (= x y))) ([x y & more] - (if (not (= x y)) + (if (not (= x y)) (loop [s #{x y} xs more] (let [x (first xs) etc (next xs)] @@ -8351,6 +8351,7 @@ reduces them without incurring seq initialization" (if (identical? node root) nil (set! root node)) + ;; FIXME: can we figure out something better here? (if ^boolean (.-val added-leaf?) (set! count (inc count))) tcoll)) @@ -8372,6 +8373,7 @@ reduces them without incurring seq initialization" (if (identical? node root) nil (set! root node)) + ;; FIXME: can we figure out something better here? (if ^boolean (.-val removed-leaf?) (set! count (dec count))) tcoll))) @@ -10562,6 +10564,7 @@ reduces them without incurring seq initialization" (pr-writer (meta obj) writer opts) (-write writer " ")) (cond + ;; FIXME: can we figure out something better here? ;; handle CLJS ctors ^boolean (.-cljs$lang$type obj) (.cljs$lang$ctorPrWriter obj obj writer opts) @@ -10576,7 +10579,7 @@ reduces them without incurring seq initialization" (number? obj) (-write writer (cond - ^boolean (js/isNaN obj) "##NaN" + (js/isNaN obj) "##NaN" (identical? obj js/Number.POSITIVE_INFINITY) "##Inf" (identical? obj js/Number.NEGATIVE_INFINITY) "##-Inf" :else (str_ obj))) @@ -11942,7 +11945,7 @@ reduces them without incurring seq initialization" (fn [x y] (cond (pred x y) -1 (pred y x) 1 :else 0))) -(defn ^boolean special-symbol? +(defn special-symbol? "Returns true if x names a special form" [x] (contains? @@ -12175,6 +12178,8 @@ reduces them without incurring seq initialization" Object (findInternedVar [this sym] (let [k (munge (str_ sym))] + ;; FIXME: this shouldn't need ^boolean due to GCL library analysis, + ;; but not currently working (when ^boolean (gobject/containsKey obj k) (let [var-sym (symbol (str_ name) (str_ sym)) var-meta {:ns this}] @@ -12268,7 +12273,7 @@ reduces them without incurring seq initialization" (when (nil? NS_CACHE) (set! NS_CACHE (atom {}))) (let [ns-str (str_ ns) - ns (if (not ^boolean (gstring/contains ns-str "$macros")) + ns (if (not (gstring/contains ns-str "$macros")) (symbol (str_ ns-str "$macros")) ns) the-ns (get @NS_CACHE ns)] @@ -12292,7 +12297,7 @@ reduces them without incurring seq initialization" [x] (instance? goog.Uri x)) -(defn ^boolean NaN? +(defn NaN? "Returns true if num is NaN, else false" [val] (js/isNaN val)) @@ -12321,6 +12326,7 @@ reduces them without incurring seq initialization" [s] (if (string? s) (cond + ;; FIXME: another cases worth thinking about ^boolean (re-matches #"[\x00-\x20]*[+-]?NaN[\x00-\x20]*" s) ##NaN ^boolean (re-matches #"[\x00-\x20]*[+-]?(Infinity|((\d+\.?\d*|\.\d+)([eE][+-]?\d+)?)[dDfF]?)[\x00-\x20]*" diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index a13c08545..2a6364c8c 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -980,10 +980,10 @@ (defn normalize-js-tag [x] ;; if not 'js, assume constructor (if-not (= 'js x) - (with-meta 'js - {:prefix (conj (->> (string/split (name x) #"\.") - (map symbol) vec) - 'prototype)}) + (let [props (->> (string/split (name x) #"\.") (map symbol)) + [xs y] ((juxt butlast last) props)] + (with-meta 'js + {:prefix (vec (concat xs [(with-meta y {:ctor true})]))})) x)) (defn ->type-set @@ -1030,46 +1030,89 @@ boolean Boolean symbol Symbol}) -(defn has-extern?* +(defn resolve-extern + "Given a foreign js property list, return a resolved js property list and the + extern var info" + ([pre] + (resolve-extern pre (get-externs))) ([pre externs] - (let [pre (if-some [me (find - (get-in externs '[Window prototype]) - (first pre))] - (if-some [tag (-> me first meta :tag)] - (into [tag 'prototype] (next pre)) - pre) - pre)] - (has-extern?* pre externs externs))) - ([pre externs top] + (resolve-extern pre externs externs {:resolved []})) + ([pre externs top ret] (cond - (empty? pre) true + (empty? pre) ret :else (let [x (first pre) me (find externs x)] (cond - (not me) false + (not me) nil :else (let [[x' externs'] me - xmeta (meta x')] - (if (and (= 'Function (:tag xmeta)) (:ctor xmeta)) - (or (has-extern?* (into '[prototype] (next pre)) externs' top) - (has-extern?* (next pre) externs' top) - ;; check base type if it exists - (when-let [super (:super xmeta)] - (has-extern?* (into [super] (next pre)) externs top))) - (recur (next pre) externs' top)))))))) + info' (meta x') + ret (cond-> ret + ;; we only care about var info for the last property + ;; also if we already added it, don't override it + ;; because we're now resolving type information + ;; not instance information anymore + ;; i.e. [console] -> [Console] but :tag is Console _not_ Function vs. + ;; [console log] -> [Console prototype log] where :tag is Function + (and (empty? (next pre)) + (not (contains? ret :info))) + (assoc :info info'))] + ;; handle actual occurrences of types, i.e. `Console` + (if (and (or (:ctor info') (:iface info')) (= 'Function (:tag info'))) + (or + ;; then check for "static" property + (resolve-extern (next pre) externs' top + (update ret :resolved conj x)) + + ;; first look for a property on the prototype + (resolve-extern (into '[prototype] (next pre)) externs' top + (update ret :resolved conj x)) + + ;; finally check the super class if there is one + (when-let [super (:super info')] + (resolve-extern (into [super] (next pre)) externs top + (assoc ret :resolved [])))) + + (or + ;; If the tag of the property isn't Function or undefined, + ;; try to resolve it similar to the super case above, + ;; this handles singleton cases like `console` + (let [tag (:tag info')] + (when (and tag (not (contains? '#{Function undefined} tag))) + ;; check prefix first, during cljs.externs parsing we always generate prefixes + ;; for tags because of types like webCrypto.Crypto + (resolve-extern (into (or (-> tag meta :prefix) [tag]) (next pre)) externs top + (assoc ret :resolved [])))) + + ;; assume static property + (recur (next pre) externs' top + (update ret :resolved conj x)))))))))) + +(defn normalize-unresolved-prefix + [pre] + (cond-> pre + (< 1 (count pre)) + (cond-> + (-> pre pop peek meta :ctor) + (-> pop + (conj 'prototype) + (conj (peek pre)))))) + +(defn has-extern?* + [pre externs] + (boolean (resolve-extern pre externs))) (defn has-extern? ([pre] (has-extern? pre (get-externs))) ([pre externs] (or (has-extern?* pre externs) - (when (= 1 (count pre)) - (let [x (first pre)] - (or (get-in externs (conj '[Window prototype] x)) - (get-in externs (conj '[Number] x))))) (-> (last pre) str (string/starts-with? "cljs$"))))) +(defn lift-tag-to-js [tag] + (symbol "js" (str (alias->type tag tag)))) + (defn js-tag ([pre] (js-tag pre :tag)) @@ -1078,12 +1121,13 @@ ([pre tag-type externs] (js-tag pre tag-type externs externs)) ([pre tag-type externs top] - (when-let [[p externs' :as me] (find externs (first pre))] - (let [tag (-> p meta tag-type)] - (if (= (count pre) 1) - (when tag (symbol "js" (str (alias->type tag tag)))) - (or (js-tag (next pre) tag-type externs' top) - (js-tag (into '[prototype] (next pre)) tag-type (get top tag) top))))))) + (when-let [tag (get-in (resolve-extern pre externs) [:info tag-type])] + (case tag + ;; don't lift these, analyze-dot will raise them for analysis + ;; representing these types as js/Foo is a hassle as it widens the + ;; return types unnecessarily i.e. #{boolean js/Boolean} + (boolean number string) tag + (lift-tag-to-js tag))))) (defn dotted-symbol? [sym] (let [s (str sym)] @@ -1274,8 +1318,9 @@ (assoc shadowed-by-local :op :local)) :else - (let [pre (->> (string/split (name sym) #"\.") (map symbol) vec)] - (when (and (not (has-extern? pre)) + (let [pre (->> (string/split (name sym) #"\.") (map symbol) vec) + res (resolve-extern (->> (string/split (name sym) #"\.") (map symbol) vec))] + (when (and (not res) ;; ignore exists? usage (not (-> sym meta ::no-resolve))) (swap! env/*compiler* update-in @@ -1284,10 +1329,12 @@ {:name sym :op :js-var :ns 'js - :tag (with-meta (or (js-tag pre) (:tag (meta sym)) 'js) {:prefix pre})} + :tag (with-meta (or (js-tag pre) (:tag (meta sym)) 'js) + {:prefix pre + :ctor (-> res :info :ctor)})} (when-let [ret-tag (js-tag pre :ret-tag)] {:js-fn-var true - :ret-tag ret-tag}))))) + :ret-tag ret-tag}))))) (let [s (str sym) lb (handle-symbol-local sym (get locals sym)) current-ns (-> env :ns :name)] @@ -2585,12 +2632,12 @@ :children [:expr]})) (def js-prim-ctor->tag - '{js/Object object - js/String string - js/Array array - js/Number number + '{js/Object object + js/String string + js/Array array + js/Number number js/Function function - js/Boolean boolean}) + js/Boolean boolean}) (defn prim-ctor? "Test whether a tag is a constructor for a JS primitive" @@ -3543,13 +3590,25 @@ (list* '. dot-form) " with classification " (classify-dot-form dot-form)))))) +;; this only for a smaller set of types that we want to infer +;; we don't generally want to consider function for example, these +;; specific cases are ones we either try to optimize or validate +(def ^{:private true} + tag->js-prim-ctor + '{string js/String + array js/Array + number js/Number + boolean js/Boolean}) + (defn analyze-dot [env target field member+ form] (let [v [target field member+] {:keys [dot-action target method field args]} (build-dot-form v) enve (assoc env :context :expr) targetexpr (analyze enve target) form-meta (meta form) - target-tag (:tag targetexpr) + target-tag (as-> (:tag targetexpr) $ + (or (some-> $ meta :ctor lift-tag-to-js) + (tag->js-prim-ctor $ $))) prop (or field method) tag (or (:tag form-meta) (and (js-tag? target-tag) @@ -3581,7 +3640,8 @@ (let [pre (-> tag meta :prefix)] (when-not (has-extern? pre) (swap! env/*compiler* update-in - (into [::namespaces (-> env :ns :name) :externs] pre) merge {})))) + (into [::namespaces (-> env :ns :name) :externs] + (normalize-unresolved-prefix pre)) merge {})))) (case dot-action ::access (let [children [:target]] {:op :host-field diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index b96c09b36..fcc03ab96 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -641,7 +641,8 @@ (defn safe-test? [env e] (let [tag (ana/infer-tag env e)] - (or (#{'boolean 'seq} tag) (truthy-constant? e)))) + (or ('#{boolean seq} (ana/js-prim-ctor->tag tag tag)) + (truthy-constant? e)))) (defmethod emit* :if [{:keys [test then else env unchecked]}] diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj index c5343e1b1..d25987cde 100644 --- a/src/main/clojure/cljs/externs.clj +++ b/src/main/clojure/cljs/externs.clj @@ -61,12 +61,23 @@ (and (= type :string-lit) (= "undefined" value))) +(defn add-prefix + "Externs inference uses :prefix meta to both resolve externs as well as generate + missing externs information. Google Closure Compiler default externs includes + nested types like webCrypto.Crypto. Add prefix information to the returned symbol to + simplify resolution later." + [type-str] + (with-meta (symbol type-str) + {:prefix (->> (string/split (name type-str) #"\.") + (map symbol) vec)})) + (defn simplify-texpr [texpr] (case (:type texpr) - :string-lit (some-> (:value texpr) symbol) - (:star :qmark) 'any - :bang (simplify-texpr (-> texpr :children first)) + :string-lit (-> texpr :value add-prefix) + :star 'any + ;; TODO: qmark should probably be #{nil T} + (:qmark :bang) (simplify-texpr (-> texpr :children first)) :pipe (let [[x y] (:children texpr)] (if (undefined? y) (simplify-texpr x) diff --git a/src/test/clojure/cljs/compiler_tests.clj b/src/test/clojure/cljs/compiler_tests.clj index bb6a9bfc3..95204e650 100644 --- a/src/test/clojure/cljs/compiler_tests.clj +++ b/src/test/clojure/cljs/compiler_tests.clj @@ -15,7 +15,8 @@ [cljs.util :as util] [cljs.tagged-literals :as tags] [clojure.java.io :as io] - [clojure.string :as str]) + [clojure.string :as str] + [clojure.test :as test]) (:import [java.io File])) (defn analyze @@ -374,6 +375,22 @@ window))]))] (is (re-find #"window__\$1" code))))) +(deftest test-externs-infer-is-nan + (testing "Not calls to truth_ if (.isNaN js/Number ...) is used as a test" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(if (.isNaN js/Number 1) true false)]))] + (is (nil? (re-find #"truth_" code)))))) + +(deftest test-goog-lib-infer-boolean + (testing "Can infer goog.string/contains returns boolean" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns test.foo + (:require [goog.string :as gstring])) + (if (gstring/contains "foobar" "foo") true false)]))] + (is (nil? (re-find #"truth_" code)))))) + ;; CLJS-1225 (comment diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 8ca7ff9aa..967164d1f 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -23,6 +23,35 @@ "goog.isArrayLike;" "Java.type;" "Object.out;" "Object.out.println;" "Object.error;" "Object.error.println;"]) +(deftest test-normalize-js-tag + (is (= 'js (ana/normalize-js-tag 'js))) + (is (= '[Foo] (-> 'js/Foo ana/normalize-js-tag meta :prefix))) + (is (true? (-> 'js/Foo ana/normalize-js-tag meta :prefix last meta :ctor))) + (is (= '[Foo Bar] (-> 'js/Foo.Bar ana/normalize-js-tag meta :prefix))) + (is (true? (-> 'js/Foo.Bar ana/normalize-js-tag meta :prefix last meta :ctor)))) + +(deftest test-normalize-unresolved-prefix + (let [pre (-> (ana/normalize-js-tag 'js/Foo) meta :prefix (conj 'bar))] + (is (= '[Foo prototype bar] (ana/normalize-unresolved-prefix pre)))) + (let [pre '[Foo bar]] + (is (= '[Foo bar] (ana/normalize-unresolved-prefix pre))))) + +(comment + + (test/test-vars [#'test-normalize-js-tag]) + (test/test-vars [#'test-normalize-unresolved-prefix]) + + ) + +(deftest test-resolve-extern + (let [externs + (externs/externs-map + (closure/load-externs + {:externs ["src/test/externs/test.js"] + :use-only-custom-externs true}))] + (is (some? (ana/resolve-extern '[baz] externs))) + (is (nil? (ana/resolve-extern '[Foo gozMethod] externs))))) + (deftest test-has-extern?-basic (let [externs (externs/externs-map (closure/load-externs @@ -35,6 +64,35 @@ (is (true? (ana/has-extern? '[baz] externs))) (is (false? (ana/has-extern? '[Baz] externs))))) +(deftest test-resolve-extern + (let [externs (externs/externs-map)] + (is (= '[Number] + (-> (ana/resolve-extern '[Number] externs) :resolved))) + (is (= '[Number prototype valueOf] + (-> (ana/resolve-extern '[Number valueOf] externs) :resolved))) + (is (= '[Console] + (-> (ana/resolve-extern '[console] externs) :resolved))) + (is (= '[Console prototype log] + (-> (ana/resolve-extern '[console log] externs) :resolved))) + (is (= '[undefined] + (-> (ana/resolve-extern '[undefined] externs) :resolved))) + (is (= '[webCrypto Crypto prototype subtle] + (-> (ana/resolve-extern '[crypto subtle] externs) :resolved))))) + +(comment + (clojure.test/test-vars [#'test-resolve-extern]) + + (def externs (externs/externs-map)) + ;; succeeds + (ana/resolve-extern '[console] externs) + (ana/resolve-extern '[console log] externs) + (ana/resolve-extern '[undefined] externs) + (ana/resolve-extern '[Number] externs) + (ana/resolve-extern '[Number isNaN] externs) + (ana/resolve-extern '[document] externs) + + ) + (deftest test-has-extern?-defaults (let [externs (externs/externs-map)] (is (true? (ana/has-extern? '[console] externs))) @@ -47,9 +105,16 @@ {:externs ["src/test/externs/test.js"]}))] (is (= 'js/Console (ana/js-tag '[console] :tag externs))) (is (= 'js/Function (ana/js-tag '[console log] :tag externs))) - (is (= 'js/Boolean (ana/js-tag '[Number isNaN] :ret-tag externs))) + (is (= 'js/undefined (ana/js-tag '[console log] :ret-tag externs))) + (is (= 'boolean (ana/js-tag '[Number isNaN] :ret-tag externs))) (is (= 'js/Foo (ana/js-tag '[baz] :ret-tag externs))))) +(comment + + (clojure.test/test-vars [#'test-js-tag]) + + ) + (defn infer-test-helper [{:keys [forms externs warnings warn js-dependency-index node-module-index with-core? opts]}] (let [test-cenv (atom @@ -82,6 +147,54 @@ (map (comp :externs second) (get @test-cenv ::ana/namespaces)))))))))))) +(deftest test-externs-type-infer + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.isNaN js/Number 1)))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(js/Number.isNaN 1)))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(let [x js/Number] + (.isNaN x 1))))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(js/isNaN 1)))) + :tag))) + (is (= 'js/Promise + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.generateKey js/crypto.subtle)))) + :tag))) + (is (= 'string + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.toUpperCase "foo")))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.isArray js/Array (array))))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.isSafeInteger js/Number 1)))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(js/isFinite 1)))) + :tag)))) + (deftest test-externs-infer (is (= 'js/Foo (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] @@ -158,9 +271,9 @@ :warnings ws})] (is (= (unsplit-lines ["Foo.Boo.prototype.wozz;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Cannot resolve property wozz for inferred type js/Foo.Boo")))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property wozz for inferred type js/Foo.Boo"))))) (deftest test-type-hint-infer-unknown-property-in-chain (let [ws (atom []) @@ -172,9 +285,9 @@ :warnings ws})] (is (= (unsplit-lines ["Foo.Boo.prototype.wozz;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Cannot resolve property wozz for inferred type js/Foo.Boo")))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property wozz for inferred type js/Foo.Boo"))))) (deftest test-type-hint-infer-unknown-method (let [ws (atom []) @@ -185,9 +298,24 @@ :warnings ws})] (is (= (unsplit-lines ["Foo.prototype.gozMethod;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Cannot resolve property gozMethod for inferred type js/Foo")))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property gozMethod for inferred type js/Foo"))))) + +(comment + + (require '[clojure.java.io :as io] + '[cljs.closure :as cc]) + + (def externs + (-> (cc/js-source-file nil (io/file "src/test/externs/test.js")) + externs/parse-externs externs/index-externs)) + + (ana/resolve-extern '[Foo gozMethod] externs) + + (clojure.test/test-vars [#'test-type-hint-infer-unknown-method]) + + ) (deftest test-infer-unknown-method-from-externs (let [ws (atom []) @@ -197,9 +325,9 @@ :warnings ws})] (is (= (unsplit-lines ["Foo.prototype.gozMethod;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Cannot resolve property gozMethod for inferred type js/Foo")))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property gozMethod for inferred type js/Foo"))))) (deftest test-infer-js-require (let [ws (atom []) @@ -211,9 +339,9 @@ :warnings ws})] (is (= (unsplit-lines ["var require;" "Object.Component;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Adding extern to Object for property Component")))) + (is (some-> @ws first + (string/starts-with? + "Adding extern to Object for property Component"))))) (deftest test-set-warn-on-infer (let [ws (atom []) @@ -227,7 +355,9 @@ :warn false :with-core? true})] (is (= 1 (count @ws))) - (is (string/starts-with? (first @ws) "Cannot infer target type")))) + (is (some-> @ws first + (string/starts-with? + "Cannot infer target type"))))) (deftest test-cljs-1970-infer-with-cljs-literals (let [ws (atom []) diff --git a/src/test/clojure/cljs/externs_parsing_tests.clj b/src/test/clojure/cljs/externs_parsing_tests.clj index ed0cfdb70..e5a399c84 100644 --- a/src/test/clojure/cljs/externs_parsing_tests.clj +++ b/src/test/clojure/cljs/externs_parsing_tests.clj @@ -37,6 +37,12 @@ (is (= 'any (get-in ns [:defs 'get :ret-tag]))) (is (= 'array (get-in ns [:defs 'getKeys :ret-tag]))))) +(comment + ;; works + (get-in (externs/analyze-goog-file "goog/object/object.js") + [:defs 'containsKey :ret-tag]) + ) + (deftest test-parse-super (let [info (-> (filter diff --git a/src/test/clojure/cljs/type_inference_tests.clj b/src/test/clojure/cljs/type_inference_tests.clj index c9c5f6343..5435cc90f 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -307,12 +307,32 @@ (is (= (env/with-compiler-env test-cenv (:tag (analyze test-env '(dissoc {:foo :bar} :foo)))) '#{clj clj-nil})) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(distinct? 1)))) + 'boolean)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(special-symbol? 'foo)))) + 'boolean)) + ;; TODO: we can't infer isa?, we get 'any which is a bit surprising + ;(is (= (env/with-compiler-env test-cenv + ; (:tag (analyze test-env '(isa? ::foo :bar)))) + ; 'boolean)) ;; has changed, why does this return #{clj any} ? ;(is (= (env/with-compiler-env test-cenv ; (:tag (analyze test-env '(assoc nil :foo :bar)))) ; 'clj)) ) +(deftest lib-inference-extern-call + (testing "Test return type inference for core fns whose + internal implementation uses standard JS APIs" + (is (= 'boolean + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(array? (array))))))) + (is (= 'array + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(make-array js/String. 10)))))))) + (deftest test-always-true-if (is (= (env/with-compiler-env test-cenv (:tag (analyze test-env '(if 1 2 "foo")))) @@ -374,3 +394,23 @@ (:import [goog.history Html5History])) (Html5History.)] {} true)))))) + +(deftest test-goog-infer + (is (= 'boolean + (:tag (env/with-compiler-env (env/default-compiler-env) + (ana/analyze-form-seq + '[(ns test.foo + (:require [goog.string :as gstring])) + (gstring/contains "foobar" "foo")] + {} true))))) + ;; FIXME: infers any instead of boolean, nothing wrong w/ the externs parsing + ;; but this definitely does not work at the moment + #_(is (= 'boolean + (:tag + (env/with-compiler-env (env/default-compiler-env) + (ana/analyze-form-seq + '[(ns test.foo + (:require [goog.object :as gobject])) + (gobject/containsKey (js-object) "foo")] + {} true)))))) + From aa5e7516e5031f81857ded5e0d2a2a476d4cfaff Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 6 Jul 2025 13:38:14 -0400 Subject: [PATCH 13/94] CLJS-3439: REPL doc support for externs (#261) * break out ->pre * add cljs.analyzer.api/resolve-extern * new doc lookup path in repl for js/foo symbols --- src/main/clojure/cljs/analyzer.cljc | 5 ++++- src/main/clojure/cljs/analyzer/api.cljc | 9 +++++++++ src/main/clojure/cljs/repl.cljc | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 2a6364c8c..ac521fa78 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -977,10 +977,13 @@ (or (= 'js x) (= "js" (namespace x))))) +(defn ->pre [x] + (->> (string/split (name x) #"\.") (map symbol))) + (defn normalize-js-tag [x] ;; if not 'js, assume constructor (if-not (= 'js x) - (let [props (->> (string/split (name x) #"\.") (map symbol)) + (let [props (->pre x) [xs y] ((juxt butlast last) props)] (with-meta 'js {:prefix (vec (concat xs [(with-meta y {:ctor true})]))})) diff --git a/src/main/clojure/cljs/analyzer/api.cljc b/src/main/clojure/cljs/analyzer/api.cljc index 2d143a42b..2fa4f2a13 100644 --- a/src/main/clojure/cljs/analyzer/api.cljc +++ b/src/main/clojure/cljs/analyzer/api.cljc @@ -218,6 +218,15 @@ ([state] (keys (get @state ::ana/namespaces)))) +(defn resolve-extern + "Given a symbol attempt to look it up in the provided externs" + ([sym] + (resolve-extern env/*compiler* sym)) + ([state sym] + (let [pre (ana/->pre sym)] + (env/with-compiler-env state + (:info (ana/resolve-extern pre)))))) + (defn find-ns "Given a namespace return the corresponding namespace analysis map. Analagous to clojure.core/find-ns." diff --git a/src/main/clojure/cljs/repl.cljc b/src/main/clojure/cljs/repl.cljc index b5c53738a..b685a62e5 100644 --- a/src/main/clojure/cljs/repl.cljc +++ b/src/main/clojure/cljs/repl.cljc @@ -1447,6 +1447,11 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) (keyword? name) `(cljs.repl/print-doc {:spec ~name :doc (cljs.spec.alpha/describe ~name)}) + (= "js" (namespace name)) + `(cljs.repl/print-doc + (quote ~(merge (select-keys (ana-api/resolve-extern name) [:doc :arglists]) + {:name name}))) + (ana-api/find-ns name) `(cljs.repl/print-doc (quote ~(select-keys (ana-api/find-ns name) [:name :doc]))) From 60c9055bb037a5111249ec6d846571bd6e57a9a3 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 7 Jul 2025 14:03:04 -0400 Subject: [PATCH 14/94] CLJS-3438: Inference for `goog.object/containsKey` returns any, not boolean (#262) - fix cljs.analyzer/desugar-dotted-expr, generated malformed AST in the case of goog.module var - compiler test for goog.object/containsKey - fix parameter parsing in cljs.externs to properly handle var args and optional arguments - fix fn-arity warning so that we use unaliased names if available (goog.module names are aliases) - cljs.core: goog.object/containsKey hint no longer needed --- src/main/cljs/cljs/core.cljs | 2 +- src/main/clojure/cljs/analyzer.cljc | 23 ++++++++---- src/main/clojure/cljs/externs.clj | 37 ++++++++++++------- src/test/clojure/cljs/compiler_tests.clj | 10 +++++ .../clojure/cljs/type_inference_tests.clj | 5 +-- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index e1c7b067e..efbde48e3 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12180,7 +12180,7 @@ reduces them without incurring seq initialization" (let [k (munge (str_ sym))] ;; FIXME: this shouldn't need ^boolean due to GCL library analysis, ;; but not currently working - (when ^boolean (gobject/containsKey obj k) + (when (gobject/containsKey obj k) (let [var-sym (symbol (str_ name) (str_ sym)) var-meta {:ns this}] (Var. (ns-lookup obj k) var-sym var-meta))))) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index ac521fa78..709531e59 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1214,9 +1214,12 @@ (defmethod resolve* :goog-module [env sym full-ns current-ns] - {:name (symbol (str current-ns) (str (munge-goog-module-lib full-ns) "." (name sym))) - :ns current-ns - :op :var}) + (let [sym-ast (gets @env/*compiler* ::namespaces full-ns :defs (symbol (name sym)))] + (merge sym-ast + {:name (symbol (str current-ns) (str (munge-goog-module-lib full-ns) "." (name sym))) + :ns current-ns + :op :var + :unaliased-name (symbol (str full-ns) (name sym))}))) (defmethod resolve* :global [env sym full-ns current-ns] @@ -3887,15 +3890,15 @@ bind-args? (and HO-invoke? (not (all-values? args)))] (when ^boolean fn-var? - (let [{^boolean variadic :variadic? :keys [max-fixed-arity method-params name ns macro]} (:info fexpr)] - ;; don't warn about invalid arity when when compiling a macros namespace + (let [{^boolean variadic :variadic? :keys [max-fixed-arity method-params name unaliased-name ns macro]} (:info fexpr)] + ;; don't warn about invalid arity when compiling a macros namespace ;; that requires itself, as that code is not meant to be executed in the ;; `$macros` ns - António Monteiro (when (and #?(:cljs (not (and (gstring/endsWith (str cur-ns) "$macros") (symbol-identical? cur-ns ns) (true? macro)))) (invalid-arity? argc method-params variadic max-fixed-arity)) - (warning :fn-arity env {:name name :argc argc})))) + (warning :fn-arity env {:name (or unaliased-name name) :argc argc})))) (when (and kw? (not (or (== 1 argc) (== 2 argc)))) (warning :fn-arity env {:name (first form) :argc argc})) (let [deprecated? (-> fexpr :info :deprecated) @@ -3946,7 +3949,10 @@ {:op :host-field :env (:env expr) :form (list '. prefix field) - :target (desugar-dotted-expr (-> expr + ;; goog.module vars get converted to the form of + ;; current.ns/goog$module.theDef, we need to dissoc + ;; actual extern var info so we get something well-formed + :target (desugar-dotted-expr (-> (dissoc expr :info) (assoc :name prefix :form prefix) (dissoc :tag) @@ -3954,6 +3960,9 @@ (assoc-in [:env :context] :expr))) :field field :tag (:tag expr) + ;; in the case of goog.module var if there is :info, + ;; we need to adopt it now as this is where :ret-tag info lives + :info (:info expr) :children [:target]}) expr) ;:var diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj index d25987cde..e7bf3014b 100644 --- a/src/main/clojure/cljs/externs.clj +++ b/src/main/clojure/cljs/externs.clj @@ -13,9 +13,9 @@ [clojure.string :as string]) (:import [com.google.javascript.jscomp CompilerOptions CompilerOptions$Environment SourceFile CompilerInput CommandLineRunner] - [com.google.javascript.jscomp.parsing Config$JsDocParsing] + [com.google.javascript.jscomp.parsing Config$JsDocParsing JsDocInfoParser$ExtendedTypeInfo] [com.google.javascript.rhino - Node Token JSTypeExpression JSDocInfo$Visibility] + Node Token JSTypeExpression JSDocInfo JSDocInfo$Visibility] [java.util.logging Level] [java.net URL])) @@ -88,14 +88,13 @@ (some-> (.getRoot texpr) parse-texpr simplify-texpr)) (defn params->method-params [xs] - (letfn [(not-opt? [x] - (not (string/starts-with? (name x) "opt_")))] - (let [required (into [] (take-while not-opt? xs)) - opts (drop-while not-opt? xs)] - (loop [ret [required] opts opts] - (if-let [opt (first opts)] - (recur (conj ret (conj (last ret) opt)) (drop 1 opts)) - (seq ret)))))) + (let [not-opt? (complement :optional?) + required (into [] (map :name (take-while not-opt? xs))) + opts (map :name (drop-while not-opt? xs))] + (loop [ret [required] opts opts] + (if-let [opt (first opts)] + (recur (conj ret (conj (last ret) opt)) (drop 1 opts)) + (seq ret))))) (defn generic? [t] (let [s (name t)] @@ -108,6 +107,18 @@ (= t 'Array) 'array :else t))) +(defn get-params + "Return param information in JSDoc appearance order. GCL is relatively + civilized, so this isn't really a problem." + [^JSDocInfo info] + (map + (fn [n] + (let [t (.getParameterType info n)] + {:name (symbol n) + :optional? (.isOptionalArg t) + :var-args? (.isVarArgs t)})) + (.getParameterNames info))) + (defn get-var-info [^Node node] (when node (let [info (.getJSDocInfo node)] @@ -124,15 +135,15 @@ (if (or (.hasReturnType info) (as-> (.getParameterCount info) c (and c (pos? c)))) - (let [arglist (into [] (map symbol (.getParameterNames info))) + (let [arglist (get-params info) arglists (params->method-params arglist)] {:tag 'Function :js-fn-var true :ret-tag (or (some-> (.getReturnType info) get-tag gtype->cljs-type) 'clj-nil) - :variadic? (boolean (some '#{var_args} arglist)) - :max-fixed-arity (count (take-while #(not= 'var_args %) arglist)) + :variadic? (boolean (some :var-args? arglist)) + :max-fixed-arity (count (take-while (complement :var-args?) arglist)) :method-params arglists :arglists arglists})))) {:file *source-file* diff --git a/src/test/clojure/cljs/compiler_tests.clj b/src/test/clojure/cljs/compiler_tests.clj index 95204e650..f6f7b560b 100644 --- a/src/test/clojure/cljs/compiler_tests.clj +++ b/src/test/clojure/cljs/compiler_tests.clj @@ -391,6 +391,16 @@ (if (gstring/contains "foobar" "foo") true false)]))] (is (nil? (re-find #"truth_" code)))))) +(deftest test-goog-module-infer-cljs-3438 + (testing "goog.object/containKey requires correct handling of vars from + goog.module namespace" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns test.foo + (:require [goog.object :as gobject])) + (if (gobject/containsKey nil nil) true false)]))] + (is (nil? (re-find #"truth_" code)))))) + ;; CLJS-1225 (comment diff --git a/src/test/clojure/cljs/type_inference_tests.clj b/src/test/clojure/cljs/type_inference_tests.clj index 5435cc90f..2bd0855c3 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -403,9 +403,7 @@ (:require [goog.string :as gstring])) (gstring/contains "foobar" "foo")] {} true))))) - ;; FIXME: infers any instead of boolean, nothing wrong w/ the externs parsing - ;; but this definitely does not work at the moment - #_(is (= 'boolean + (is (= 'boolean (:tag (env/with-compiler-env (env/default-compiler-env) (ana/analyze-form-seq @@ -413,4 +411,3 @@ (:require [goog.object :as gobject])) (gobject/containsKey (js-object) "foo")] {} true)))))) - From e1328b7b99d376335bde3e5a0d59c6fab85cebeb Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 7 Jul 2025 14:07:08 -0400 Subject: [PATCH 15/94] - remove unused import from last commit --- src/main/clojure/cljs/externs.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj index e7bf3014b..e354aac74 100644 --- a/src/main/clojure/cljs/externs.clj +++ b/src/main/clojure/cljs/externs.clj @@ -13,7 +13,7 @@ [clojure.string :as string]) (:import [com.google.javascript.jscomp CompilerOptions CompilerOptions$Environment SourceFile CompilerInput CommandLineRunner] - [com.google.javascript.jscomp.parsing Config$JsDocParsing JsDocInfoParser$ExtendedTypeInfo] + [com.google.javascript.jscomp.parsing Config$JsDocParsing] [com.google.javascript.rhino Node Token JSTypeExpression JSDocInfo JSDocInfo$Visibility] [java.util.logging Level] From 5027991ea61a2018f008d1f6a81cef025019ee8d Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 7 Jul 2025 15:45:30 -0400 Subject: [PATCH 16/94] remove FIXME comment for gobj/containsKey usage in cljs.core --- src/main/cljs/cljs/core.cljs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index efbde48e3..f70e544f8 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12178,8 +12178,6 @@ reduces them without incurring seq initialization" Object (findInternedVar [this sym] (let [k (munge (str_ sym))] - ;; FIXME: this shouldn't need ^boolean due to GCL library analysis, - ;; but not currently working (when (gobject/containsKey obj k) (let [var-sym (symbol (str_ name) (str_ sym)) var-meta {:ns this}] From 89506d4d6109d34bfbf50c2dfd3b3bf58c9fe642 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 17:57:25 -0400 Subject: [PATCH 17/94] Copy over the 2011 copy-on-write versions of the data structures --- src/main/cljs/cljs/core.cljs | 501 ++++++++++++++++++++++++----------- 1 file changed, 343 insertions(+), 158 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index f70e544f8..57d578867 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -6560,164 +6560,7 @@ reduces them without incurring seq initialization" (if (identical? k (aget array i)) i (recur (+ i incr))))))) - -; The keys field is an array of all keys of this map, in no particular -; order. Any string, keyword, or symbol key is used as a property name -; to store the value in strobj. If a key is assoc'ed when that same -; key already exists in strobj, the old value is overwritten. If a -; non-string key is assoc'ed, return a HashMap object instead. - -(defn- obj-map-compare-keys [a b] - (let [a (hash a) - b (hash b)] - (cond - (< a b) -1 - (> a b) 1 - :else 0))) - -(defn- obj-map->hash-map [m k v] - (let [ks (.-keys m) - len (alength ks) - so (.-strobj m) - mm (meta m)] - (loop [i 0 - out (transient (.-EMPTY PersistentHashMap))] - (if (< i len) - (let [k (aget ks i)] - (recur (inc i) (assoc! out k (gobject/get so k)))) - (-with-meta (persistent! (assoc! out k v)) mm))))) - -;;; ObjMap - DEPRECATED - -(defn- obj-clone [obj ks] - (let [new-obj (js-obj) - l (alength ks)] - (loop [i 0] - (when (< i l) - (let [k (aget ks i)] - (gobject/set new-obj k (gobject/get obj k)) - (recur (inc i))))) - new-obj)) - -(deftype ObjMap [meta keys strobj update-count ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - (equiv [this other] - (-equiv this other)) - - IWithMeta - (-with-meta [coll new-meta] - (if (identical? new-meta meta) - coll - (ObjMap. new-meta keys strobj update-count __hash))) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (-with-meta (.-EMPTY ObjMap) meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) - - ISeqable - (-seq [coll] - (when (pos? (alength keys)) - (map #(vector % (unchecked-get strobj %)) - (.sort keys obj-map-compare-keys)))) - - ICounted - (-count [coll] (alength keys)) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (unchecked-get strobj k) - not-found)) - - IAssociative - (-assoc [coll k v] - (if (string? k) - (if (or (> update-count (.-HASHMAP_THRESHOLD ObjMap)) - (>= (alength keys) (.-HASHMAP_THRESHOLD ObjMap))) - (obj-map->hash-map coll k v) - (if-not (nil? (scan-array 1 k keys)) - (let [new-strobj (obj-clone strobj keys)] - (gobject/set new-strobj k v) - (ObjMap. meta keys new-strobj (inc update-count) nil)) ; overwrite - (let [new-strobj (obj-clone strobj keys) ; append - new-keys (aclone keys)] - (gobject/set new-strobj k v) - (.push new-keys k) - (ObjMap. meta new-keys new-strobj (inc update-count) nil)))) - ;; non-string key. game over. - (obj-map->hash-map coll k v))) - (-contains-key? [coll k] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - true - false)) - - IFind - (-find [coll k] - (when (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (MapEntry. k (unchecked-get strobj k) nil))) - - IKVReduce - (-kv-reduce [coll f init] - (let [len (alength keys)] - (loop [keys (.sort keys obj-map-compare-keys) - init init] - (if (seq keys) - (let [k (first keys) - init (f init k (unchecked-get strobj k))] - (if (reduced? init) - @init - (recur (rest keys) init))) - init)))) - - IMap - (-dissoc [coll k] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (let [new-keys (aclone keys) - new-strobj (obj-clone strobj keys)] - (.splice new-keys (scan-array 1 k new-keys) 1) - (js-delete new-strobj k) - (ObjMap. meta new-keys new-strobj (inc update-count) nil)) - coll)) ; key not found, return coll unchanged - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IEditableCollection - (-as-transient [coll] - (transient (into (hash-map) coll)))) - -(set! (.-EMPTY ObjMap) (ObjMap. nil (array) (js-obj) 0 empty-unordered-hash)) - -(set! (.-HASHMAP_THRESHOLD ObjMap) 8) - -(set! (.-fromObject ObjMap) (fn [ks obj] (ObjMap. nil ks obj 0 nil))) - + ;; Record Iterator (deftype RecordIter [^:mutable i record base-count fields ext-map-iter] Object @@ -12400,3 +12243,345 @@ reduces them without incurring seq initialization" (identical? "window" *global*) (set! goog/global js/window) (identical? "self" *global*) (set! goog/global js/self) (identical? "global" *global*) (set! goog/global js/global))) + +;; ----------------------------------------------------------------------------- +;; Original 2011 Copy-on-Write Types + +;;; Vector + +(deftype Vector [meta array] + IWithMeta + (-with-meta [coll meta] (Vector. meta array)) + + IMeta + (-meta [coll] meta) + + IStack + (-peek [coll] + (let [count (.-length array)] + (when (> count 0) + (aget array (dec count))))) + (-pop [coll] + (if (> (.-length array) 0) + (let [new-array (aclone array)] + (. new-array (pop)) + (Vector. meta new-array)) + (throw (js/Error. "Can't pop empty vector")))) + + ICollection + (-conj [coll o] + (let [new-array (aclone array)] + (.push new-array o) + (Vector. meta new-array))) + + IEmptyableCollection + (-empty [coll] (with-meta cljs.core.Vector/EMPTY meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] + (when (> (.-length array) 0) + (let [vector-seq + (fn vector-seq [i] + (lazy-seq + (when (< i (.-length array)) + (cons (aget array i) (vector-seq (inc i))))))] + (vector-seq 0)))) + + ICounted + (-count [coll] (.-length array)) + + IIndexed + (-nth [coll n] + (if (and (<= 0 n) (< n (.-length array))) + (aget array n) + #_(throw (js/Error. (str "No item " n " in vector of length " (.-length array)))))) + (-nth [coll n not-found] + (if (and (<= 0 n) (< n (.-length array))) + (aget array n) + not-found)) + + ILookup + (-lookup [coll k] (-nth coll k nil)) + (-lookup [coll k not-found] (-nth coll k not-found)) + + IAssociative + (-assoc [coll k v] + (let [new-array (aclone array)] + (aset new-array k v) + (Vector. meta new-array))) + + IVector + (-assoc-n [coll n val] (-assoc coll n val)) + + IReduce + (-reduce [v f] + (ci-reduce array f)) + (-reduce [v f start] + (ci-reduce array f start)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (. Vector -EMPTY) (Vector. nil (array))) + +(set! (. Vector -fromArray) (fn [xs] (Vector. nil xs))) + +; The keys field is an array of all keys of this map, in no particular +; order. Any string, keyword, or symbol key is used as a property name +; to store the value in strobj. If a key is assoc'ed when that same +; key already exists in strobj, the old value is overwritten. If a +; non-string key is assoc'ed, return a HashMap object instead. + +(defn- obj-map-contains-key? + ([k strobj] + (obj-map-contains-key? k strobj true false)) + ([k strobj true-val false-val] + (if (and (goog/isString k) (.hasOwnProperty strobj k)) + true-val + false-val))) + +(defn- obj-map-compare-keys [a b] + (let [a (hash a) + b (hash b)] + (cond + (< a b) -1 + (> a b) 1 + :else 0))) + +(deftype ObjMap [meta keys strobj] + IWithMeta + (-with-meta [coll meta] (ObjMap. meta keys strobj)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj + coll + entry))) + + IEmptyableCollection + (-empty [coll] (with-meta cljs.core.ObjMap/EMPTY meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] + (when (pos? (.-length keys)) + (map #(vector % (aget strobj %)) + (.sort keys obj-map-compare-keys)))) + + ICounted + (-count [coll] (.-length keys)) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (obj-map-contains-key? k strobj (aget strobj k) not-found)) + + IAssociative + (-assoc [coll k v] + (if (goog/isString k) + (let [new-strobj (goog.object/clone strobj) + overwrite? (.hasOwnProperty new-strobj k)] + (aset new-strobj k v) + (if overwrite? + (ObjMap. meta keys new-strobj) ; overwrite + (let [new-keys (aclone keys)] ; append + (.push new-keys k) + (ObjMap. meta new-keys new-strobj)))) + ; non-string key. game over. + (with-meta (into (hash-map k v) (seq coll)) meta))) + (-contains-key? [coll k] + (obj-map-contains-key? k strobj)) + + IMap + (-dissoc [coll k] + (if (and (goog/isString k) (.hasOwnProperty strobj k)) + (let [new-keys (aclone keys) + new-strobj (goog.object/clone strobj)] + (.splice new-keys (scan-array 1 k new-keys) 1) + (js-delete new-strobj k) + (ObjMap. meta new-keys new-strobj)) + coll)) ; key not found, return coll unchanged + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj))) + +(set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj))) + +; The keys field is an array of all keys of this map, in no particular +; order. Each key is hashed and the result used as a property name of +; hashobj. Each values in hashobj is actually a bucket in order to handle hash +; collisions. A bucket is an array of alternating keys (not their hashes) and +; vals. +(deftype HashMap [meta count hashobj] + IWithMeta + (-with-meta [coll meta] (HashMap. meta count hashobj)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj + coll + entry))) + + IEmptyableCollection + (-empty [coll] (with-meta cljs.core.HashMap/EMPTY meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] + (when (pos? count) + (let [hashes (.sort (js-keys hashobj))] + (mapcat #(map vec (partition 2 (aget hashobj %))) + hashes)))) + + ICounted + (-count [coll] count) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (let [bucket (aget hashobj (hash k)) + i (when bucket (scan-array 2 k bucket))] + (if i + (aget bucket (inc i)) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [h (hash k) + bucket (aget hashobj h)] + (if bucket + (let [new-bucket (aclone bucket) + new-hashobj (goog.object/clone hashobj)] + (aset new-hashobj h new-bucket) + (if-let [i (scan-array 2 k new-bucket)] + (do ; found key, replace + (aset new-bucket (inc i) v) + (HashMap. meta count new-hashobj)) + (do ; did not find key, append + (.push new-bucket k v) + (HashMap. meta (inc count) new-hashobj)))) + (let [new-hashobj (goog.object/clone hashobj)] ; did not find bucket + (aset new-hashobj h (array k v)) + (HashMap. meta (inc count) new-hashobj))))) + (-contains-key? [coll k] + (let [bucket (aget hashobj (hash k)) + i (when bucket (scan-array 2 k bucket))] + (if i + true + false))) + + IMap + (-dissoc [coll k] + (let [h (hash k) + bucket (aget hashobj h) + i (when bucket (scan-array 2 k bucket))] + (if (not i) + coll ; key not found, return coll unchanged + (let [new-hashobj (goog.object/clone hashobj)] + (if (> 3 (.-length bucket)) + (js-delete new-hashobj h) + (let [new-bucket (aclone bucket)] + (.splice new-bucket i 2) + (aset new-hashobj h new-bucket))) + (HashMap. meta (dec count) new-hashobj))))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj))) + +(set! cljs.core.HashMap/fromArrays (fn [ks vs] + (let [len (.-length ks)] + (loop [i 0, out cljs.core.HashMap/EMPTY] + (if (< i len) + (recur (inc i) (assoc out (aget ks i) (aget vs i))) + out))))) + +(deftype Set [meta hash-map] + IWithMeta + (-with-meta [coll meta] (Set. meta hash-map)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll o] + (Set. meta (assoc hash-map o nil))) + + IEmptyableCollection + (-empty [coll] (with-meta cljs.core.Set/EMPTY meta)) + + IEquiv + (-equiv [coll other] + (and + (set? other) + (= (count coll) (count other)) + (every? #(contains? coll %) + other))) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] (keys hash-map)) + + ICounted + (-count [coll] (count (seq coll))) + + ILookup + (-lookup [coll v] + (-lookup coll v nil)) + (-lookup [coll v not-found] + (if (-contains-key? hash-map v) + v + not-found)) + + ISet + (-disjoin [coll v] + (Set. meta (dissoc hash-map v))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (. Set -EMPTY) (Set. nil (hash-map))) From 45e34d261c3abd7d8d5642fee4b1cfa891119a92 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 18:03:15 -0400 Subject: [PATCH 18/94] revert additions to master. Keep ObjMap removed for now. --- src/main/cljs/cljs/core.cljs | 342 ----------------------------------- 1 file changed, 342 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 57d578867..8458f7e16 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12243,345 +12243,3 @@ reduces them without incurring seq initialization" (identical? "window" *global*) (set! goog/global js/window) (identical? "self" *global*) (set! goog/global js/self) (identical? "global" *global*) (set! goog/global js/global))) - -;; ----------------------------------------------------------------------------- -;; Original 2011 Copy-on-Write Types - -;;; Vector - -(deftype Vector [meta array] - IWithMeta - (-with-meta [coll meta] (Vector. meta array)) - - IMeta - (-meta [coll] meta) - - IStack - (-peek [coll] - (let [count (.-length array)] - (when (> count 0) - (aget array (dec count))))) - (-pop [coll] - (if (> (.-length array) 0) - (let [new-array (aclone array)] - (. new-array (pop)) - (Vector. meta new-array)) - (throw (js/Error. "Can't pop empty vector")))) - - ICollection - (-conj [coll o] - (let [new-array (aclone array)] - (.push new-array o) - (Vector. meta new-array))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.Vector/EMPTY meta)) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeqable - (-seq [coll] - (when (> (.-length array) 0) - (let [vector-seq - (fn vector-seq [i] - (lazy-seq - (when (< i (.-length array)) - (cons (aget array i) (vector-seq (inc i))))))] - (vector-seq 0)))) - - ICounted - (-count [coll] (.-length array)) - - IIndexed - (-nth [coll n] - (if (and (<= 0 n) (< n (.-length array))) - (aget array n) - #_(throw (js/Error. (str "No item " n " in vector of length " (.-length array)))))) - (-nth [coll n not-found] - (if (and (<= 0 n) (< n (.-length array))) - (aget array n) - not-found)) - - ILookup - (-lookup [coll k] (-nth coll k nil)) - (-lookup [coll k not-found] (-nth coll k not-found)) - - IAssociative - (-assoc [coll k v] - (let [new-array (aclone array)] - (aset new-array k v) - (Vector. meta new-array))) - - IVector - (-assoc-n [coll n val] (-assoc coll n val)) - - IReduce - (-reduce [v f] - (ci-reduce array f)) - (-reduce [v f start] - (ci-reduce array f start)) - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! (. Vector -EMPTY) (Vector. nil (array))) - -(set! (. Vector -fromArray) (fn [xs] (Vector. nil xs))) - -; The keys field is an array of all keys of this map, in no particular -; order. Any string, keyword, or symbol key is used as a property name -; to store the value in strobj. If a key is assoc'ed when that same -; key already exists in strobj, the old value is overwritten. If a -; non-string key is assoc'ed, return a HashMap object instead. - -(defn- obj-map-contains-key? - ([k strobj] - (obj-map-contains-key? k strobj true false)) - ([k strobj true-val false-val] - (if (and (goog/isString k) (.hasOwnProperty strobj k)) - true-val - false-val))) - -(defn- obj-map-compare-keys [a b] - (let [a (hash a) - b (hash b)] - (cond - (< a b) -1 - (> a b) 1 - :else 0))) - -(deftype ObjMap [meta keys strobj] - IWithMeta - (-with-meta [coll meta] (ObjMap. meta keys strobj)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.ObjMap/EMPTY meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeqable - (-seq [coll] - (when (pos? (.-length keys)) - (map #(vector % (aget strobj %)) - (.sort keys obj-map-compare-keys)))) - - ICounted - (-count [coll] (.-length keys)) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] - (obj-map-contains-key? k strobj (aget strobj k) not-found)) - - IAssociative - (-assoc [coll k v] - (if (goog/isString k) - (let [new-strobj (goog.object/clone strobj) - overwrite? (.hasOwnProperty new-strobj k)] - (aset new-strobj k v) - (if overwrite? - (ObjMap. meta keys new-strobj) ; overwrite - (let [new-keys (aclone keys)] ; append - (.push new-keys k) - (ObjMap. meta new-keys new-strobj)))) - ; non-string key. game over. - (with-meta (into (hash-map k v) (seq coll)) meta))) - (-contains-key? [coll k] - (obj-map-contains-key? k strobj)) - - IMap - (-dissoc [coll k] - (if (and (goog/isString k) (.hasOwnProperty strobj k)) - (let [new-keys (aclone keys) - new-strobj (goog.object/clone strobj)] - (.splice new-keys (scan-array 1 k new-keys) 1) - (js-delete new-strobj k) - (ObjMap. meta new-keys new-strobj)) - coll)) ; key not found, return coll unchanged - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj))) - -(set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj))) - -; The keys field is an array of all keys of this map, in no particular -; order. Each key is hashed and the result used as a property name of -; hashobj. Each values in hashobj is actually a bucket in order to handle hash -; collisions. A bucket is an array of alternating keys (not their hashes) and -; vals. -(deftype HashMap [meta count hashobj] - IWithMeta - (-with-meta [coll meta] (HashMap. meta count hashobj)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.HashMap/EMPTY meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeqable - (-seq [coll] - (when (pos? count) - (let [hashes (.sort (js-keys hashobj))] - (mapcat #(map vec (partition 2 (aget hashobj %))) - hashes)))) - - ICounted - (-count [coll] count) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] - (let [bucket (aget hashobj (hash k)) - i (when bucket (scan-array 2 k bucket))] - (if i - (aget bucket (inc i)) - not-found))) - - IAssociative - (-assoc [coll k v] - (let [h (hash k) - bucket (aget hashobj h)] - (if bucket - (let [new-bucket (aclone bucket) - new-hashobj (goog.object/clone hashobj)] - (aset new-hashobj h new-bucket) - (if-let [i (scan-array 2 k new-bucket)] - (do ; found key, replace - (aset new-bucket (inc i) v) - (HashMap. meta count new-hashobj)) - (do ; did not find key, append - (.push new-bucket k v) - (HashMap. meta (inc count) new-hashobj)))) - (let [new-hashobj (goog.object/clone hashobj)] ; did not find bucket - (aset new-hashobj h (array k v)) - (HashMap. meta (inc count) new-hashobj))))) - (-contains-key? [coll k] - (let [bucket (aget hashobj (hash k)) - i (when bucket (scan-array 2 k bucket))] - (if i - true - false))) - - IMap - (-dissoc [coll k] - (let [h (hash k) - bucket (aget hashobj h) - i (when bucket (scan-array 2 k bucket))] - (if (not i) - coll ; key not found, return coll unchanged - (let [new-hashobj (goog.object/clone hashobj)] - (if (> 3 (.-length bucket)) - (js-delete new-hashobj h) - (let [new-bucket (aclone bucket)] - (.splice new-bucket i 2) - (aset new-hashobj h new-bucket))) - (HashMap. meta (dec count) new-hashobj))))) - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj))) - -(set! cljs.core.HashMap/fromArrays (fn [ks vs] - (let [len (.-length ks)] - (loop [i 0, out cljs.core.HashMap/EMPTY] - (if (< i len) - (recur (inc i) (assoc out (aget ks i) (aget vs i))) - out))))) - -(deftype Set [meta hash-map] - IWithMeta - (-with-meta [coll meta] (Set. meta hash-map)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll o] - (Set. meta (assoc hash-map o nil))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.Set/EMPTY meta)) - - IEquiv - (-equiv [coll other] - (and - (set? other) - (= (count coll) (count other)) - (every? #(contains? coll %) - other))) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeqable - (-seq [coll] (keys hash-map)) - - ICounted - (-count [coll] (count (seq coll))) - - ILookup - (-lookup [coll v] - (-lookup coll v nil)) - (-lookup [coll v not-found] - (if (-contains-key? hash-map v) - v - not-found)) - - ISet - (-disjoin [coll v] - (Set. meta (dissoc hash-map v))) - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! (. Set -EMPTY) (Set. nil (hash-map))) From 9c4f2efc1233922706827a31cb92b00c5e2a8768 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 18:17:50 -0400 Subject: [PATCH 19/94] - remove straggling ObjMap stuff - cleanup ObjMap tests, either comment out, or remove things which will no longer be relevant (promotion to PHM) --- src/main/cljs/cljs/core.cljs | 17 ----------------- src/test/cljs/cljs/collections_test.cljs | 10 ++-------- src/test/cljs/cljs/core_test.cljs | 4 ++-- 3 files changed, 4 insertions(+), 27 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 8458f7e16..cae817d98 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -9034,19 +9034,6 @@ reduces them without incurring seq initialization" (.createAsIfByAssoc PersistentArrayMap (to-array s)) (if (seq s) (first s) (.-EMPTY PersistentArrayMap)))) -(defn obj-map - "keyval => key val - Returns a new object map with supplied mappings." - [& keyvals] - (let [ks (array) - obj (js-obj)] - (loop [kvs (seq keyvals)] - (if kvs - (do (.push ks (first kvs)) - (gobject/set obj (first kvs) (second kvs)) - (recur (nnext kvs))) - (.fromObject ObjMap ks obj))))) - (defn sorted-map "keyval => key val Returns a new sorted map with supplied mappings." @@ -10698,10 +10685,6 @@ reduces them without incurring seq initialization" MapEntry (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) - ObjMap - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts)) - KeySeq (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) diff --git a/src/test/cljs/cljs/collections_test.cljs b/src/test/cljs/cljs/collections_test.cljs index 3c85985c0..44d5e3f46 100644 --- a/src/test/cljs/cljs/collections_test.cljs +++ b/src/test/cljs/cljs/collections_test.cljs @@ -21,8 +21,8 @@ (is (= {:a :b} (get {[1 2 3] {:a :b}, 4 5} [1 2 3]))) (is (not (= {:a :b :c nil} {:a :b :d nil}))) (is (= {:a :b} (dissoc {:a :b :c :d} :c))) - (is (= (hash-map :foo 5) - (assoc (cljs.core.ObjMap. nil (array) (js-obj)) :foo 5)))) + #_(is (= (hash-map :foo 5) + (assoc (ObjMap. nil (array) (js-obj)) :foo 5)))) (testing "Testing assoc dissoc" (is (= {1 2 3 4} (assoc {} 1 2 3 4))) (is (= {1 2} (assoc {} 1 2))) @@ -879,12 +879,6 @@ (deftest test-461 ;; CLJS-461: automatic map conversions - (loop [i 0 m (with-meta {} {:foo :bar}) result []] - (if (<= i (+ cljs.core.ObjMap.HASHMAP_THRESHOLD 2)) - (recur (inc i) (assoc m (str i) i) (conj result (meta m))) - (let [n (inc (+ cljs.core.ObjMap.HASHMAP_THRESHOLD 2)) - expected (repeat n {:foo :bar})] - (is (= result expected))))) (loop [i 0 m (with-meta {-1 :quux} {:foo :bar}) result []] (if (<= i (+ cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD 2)) (recur (inc i) (assoc m i i) (conj result (meta m))) diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index 58720b5a1..9d9b4306e 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -196,9 +196,9 @@ (is (= #{:cljs.core-test/rect :cljs.core-test/square} (descendants ::shape))) (is (true? (isa? 42 42))) (is (true? (isa? ::square ::shape))) - (derive cljs.core.ObjMap ::collection) + ;(derive ObjMap ::collection) (derive cljs.core.PersistentHashSet ::collection) - (is (true? (isa? cljs.core.ObjMap ::collection))) + ;(is (true? (isa? ObjMap ::collection))) (is (true? (isa? cljs.core.PersistentHashSet ::collection))) (is (false? (isa? cljs.core.IndexedSeq ::collection))) ;; ?? (isa? String Object) From e5c984041d150b6c67ad5756d34b3b86fbd6133f Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 18:44:17 -0400 Subject: [PATCH 20/94] fully revert the obj-map changes for now, not worth the hassle - it's coming back anyway. so the only real change is the removal of the promotion test which we want to keep. --- src/main/cljs/cljs/core.cljs | 176 ++++++++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 1 deletion(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index cae817d98..4305440a8 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -6560,7 +6560,164 @@ reduces them without incurring seq initialization" (if (identical? k (aget array i)) i (recur (+ i incr))))))) - + +; The keys field is an array of all keys of this map, in no particular +; order. Any string, keyword, or symbol key is used as a property name +; to store the value in strobj. If a key is assoc'ed when that same +; key already exists in strobj, the old value is overwritten. If a +; non-string key is assoc'ed, return a HashMap object instead. + +(defn- obj-map-compare-keys [a b] + (let [a (hash a) + b (hash b)] + (cond + (< a b) -1 + (> a b) 1 + :else 0))) + +(defn- obj-map->hash-map [m k v] + (let [ks (.-keys m) + len (alength ks) + so (.-strobj m) + mm (meta m)] + (loop [i 0 + out (transient (.-EMPTY PersistentHashMap))] + (if (< i len) + (let [k (aget ks i)] + (recur (inc i) (assoc! out k (gobject/get so k)))) + (-with-meta (persistent! (assoc! out k v)) mm))))) + +;;; ObjMap - DEPRECATED + +(defn- obj-clone [obj ks] + (let [new-obj (js-obj) + l (alength ks)] + (loop [i 0] + (when (< i l) + (let [k (aget ks i)] + (gobject/set new-obj k (gobject/get obj k)) + (recur (inc i))))) + new-obj)) + +(deftype ObjMap [meta keys strobj update-count ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (ObjMap. new-meta keys strobj update-count __hash))) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj + coll + entry))) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY ObjMap) meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (when (pos? (alength keys)) + (map #(vector % (unchecked-get strobj %)) + (.sort keys obj-map-compare-keys)))) + + ICounted + (-count [coll] (alength keys)) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (if (and (string? k) + (not (nil? (scan-array 1 k keys)))) + (unchecked-get strobj k) + not-found)) + + IAssociative + (-assoc [coll k v] + (if (string? k) + (if (or (> update-count (.-HASHMAP_THRESHOLD ObjMap)) + (>= (alength keys) (.-HASHMAP_THRESHOLD ObjMap))) + (obj-map->hash-map coll k v) + (if-not (nil? (scan-array 1 k keys)) + (let [new-strobj (obj-clone strobj keys)] + (gobject/set new-strobj k v) + (ObjMap. meta keys new-strobj (inc update-count) nil)) ; overwrite + (let [new-strobj (obj-clone strobj keys) ; append + new-keys (aclone keys)] + (gobject/set new-strobj k v) + (.push new-keys k) + (ObjMap. meta new-keys new-strobj (inc update-count) nil)))) + ;; non-string key. game over. + (obj-map->hash-map coll k v))) + (-contains-key? [coll k] + (if (and (string? k) + (not (nil? (scan-array 1 k keys)))) + true + false)) + + IFind + (-find [coll k] + (when (and (string? k) + (not (nil? (scan-array 1 k keys)))) + (MapEntry. k (unchecked-get strobj k) nil))) + + IKVReduce + (-kv-reduce [coll f init] + (let [len (alength keys)] + (loop [keys (.sort keys obj-map-compare-keys) + init init] + (if (seq keys) + (let [k (first keys) + init (f init k (unchecked-get strobj k))] + (if (reduced? init) + @init + (recur (rest keys) init))) + init)))) + + IMap + (-dissoc [coll k] + (if (and (string? k) + (not (nil? (scan-array 1 k keys)))) + (let [new-keys (aclone keys) + new-strobj (obj-clone strobj keys)] + (.splice new-keys (scan-array 1 k new-keys) 1) + (js-delete new-strobj k) + (ObjMap. meta new-keys new-strobj (inc update-count) nil)) + coll)) ; key not found, return coll unchanged + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + (transient (into (hash-map) coll)))) + +(set! (.-EMPTY ObjMap) (ObjMap. nil (array) (js-obj) 0 empty-unordered-hash)) + +(set! (.-HASHMAP_THRESHOLD ObjMap) 8) + +(set! (.-fromObject ObjMap) (fn [ks obj] (ObjMap. nil ks obj 0 nil))) + ;; Record Iterator (deftype RecordIter [^:mutable i record base-count fields ext-map-iter] Object @@ -9034,6 +9191,19 @@ reduces them without incurring seq initialization" (.createAsIfByAssoc PersistentArrayMap (to-array s)) (if (seq s) (first s) (.-EMPTY PersistentArrayMap)))) +(defn obj-map + "keyval => key val + Returns a new object map with supplied mappings." + [& keyvals] + (let [ks (array) + obj (js-obj)] + (loop [kvs (seq keyvals)] + (if kvs + (do (.push ks (first kvs)) + (gobject/set obj (first kvs) (second kvs)) + (recur (nnext kvs))) + (.fromObject ObjMap ks obj))))) + (defn sorted-map "keyval => key val Returns a new sorted map with supplied mappings." @@ -10685,6 +10855,10 @@ reduces them without incurring seq initialization" MapEntry (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) + ObjMap + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts)) + KeySeq (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) From d998f1384cfc2de437f8f4dbec50528f0f08e688 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 18:06:39 -0400 Subject: [PATCH 21/94] readd the changes --- src/main/cljs/cljs/core.cljs | 342 +++++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 4305440a8..a76f1f191 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12400,3 +12400,345 @@ reduces them without incurring seq initialization" (identical? "window" *global*) (set! goog/global js/window) (identical? "self" *global*) (set! goog/global js/self) (identical? "global" *global*) (set! goog/global js/global))) + +;; ----------------------------------------------------------------------------- +;; Original 2011 Copy-on-Write Types + +;;; Vector + +(deftype Vector [meta array] + IWithMeta + (-with-meta [coll meta] (Vector. meta array)) + + IMeta + (-meta [coll] meta) + + IStack + (-peek [coll] + (let [count (.-length array)] + (when (> count 0) + (aget array (dec count))))) + (-pop [coll] + (if (> (.-length array) 0) + (let [new-array (aclone array)] + (. new-array (pop)) + (Vector. meta new-array)) + (throw (js/Error. "Can't pop empty vector")))) + + ICollection + (-conj [coll o] + (let [new-array (aclone array)] + (.push new-array o) + (Vector. meta new-array))) + + IEmptyableCollection + (-empty [coll] (with-meta cljs.core.Vector/EMPTY meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] + (when (> (.-length array) 0) + (let [vector-seq + (fn vector-seq [i] + (lazy-seq + (when (< i (.-length array)) + (cons (aget array i) (vector-seq (inc i))))))] + (vector-seq 0)))) + + ICounted + (-count [coll] (.-length array)) + + IIndexed + (-nth [coll n] + (if (and (<= 0 n) (< n (.-length array))) + (aget array n) + #_(throw (js/Error. (str "No item " n " in vector of length " (.-length array)))))) + (-nth [coll n not-found] + (if (and (<= 0 n) (< n (.-length array))) + (aget array n) + not-found)) + + ILookup + (-lookup [coll k] (-nth coll k nil)) + (-lookup [coll k not-found] (-nth coll k not-found)) + + IAssociative + (-assoc [coll k v] + (let [new-array (aclone array)] + (aset new-array k v) + (Vector. meta new-array))) + + IVector + (-assoc-n [coll n val] (-assoc coll n val)) + + IReduce + (-reduce [v f] + (ci-reduce array f)) + (-reduce [v f start] + (ci-reduce array f start)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (. Vector -EMPTY) (Vector. nil (array))) + +(set! (. Vector -fromArray) (fn [xs] (Vector. nil xs))) + +; The keys field is an array of all keys of this map, in no particular +; order. Any string, keyword, or symbol key is used as a property name +; to store the value in strobj. If a key is assoc'ed when that same +; key already exists in strobj, the old value is overwritten. If a +; non-string key is assoc'ed, return a HashMap object instead. + +(defn- obj-map-contains-key? + ([k strobj] + (obj-map-contains-key? k strobj true false)) + ([k strobj true-val false-val] + (if (and (goog/isString k) (.hasOwnProperty strobj k)) + true-val + false-val))) + +(defn- obj-map-compare-keys [a b] + (let [a (hash a) + b (hash b)] + (cond + (< a b) -1 + (> a b) 1 + :else 0))) + +(deftype ObjMap [meta keys strobj] + IWithMeta + (-with-meta [coll meta] (ObjMap. meta keys strobj)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj + coll + entry))) + + IEmptyableCollection + (-empty [coll] (with-meta cljs.core.ObjMap/EMPTY meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] + (when (pos? (.-length keys)) + (map #(vector % (aget strobj %)) + (.sort keys obj-map-compare-keys)))) + + ICounted + (-count [coll] (.-length keys)) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (obj-map-contains-key? k strobj (aget strobj k) not-found)) + + IAssociative + (-assoc [coll k v] + (if (goog/isString k) + (let [new-strobj (goog.object/clone strobj) + overwrite? (.hasOwnProperty new-strobj k)] + (aset new-strobj k v) + (if overwrite? + (ObjMap. meta keys new-strobj) ; overwrite + (let [new-keys (aclone keys)] ; append + (.push new-keys k) + (ObjMap. meta new-keys new-strobj)))) + ; non-string key. game over. + (with-meta (into (hash-map k v) (seq coll)) meta))) + (-contains-key? [coll k] + (obj-map-contains-key? k strobj)) + + IMap + (-dissoc [coll k] + (if (and (goog/isString k) (.hasOwnProperty strobj k)) + (let [new-keys (aclone keys) + new-strobj (goog.object/clone strobj)] + (.splice new-keys (scan-array 1 k new-keys) 1) + (js-delete new-strobj k) + (ObjMap. meta new-keys new-strobj)) + coll)) ; key not found, return coll unchanged + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj))) + +(set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj))) + +; The keys field is an array of all keys of this map, in no particular +; order. Each key is hashed and the result used as a property name of +; hashobj. Each values in hashobj is actually a bucket in order to handle hash +; collisions. A bucket is an array of alternating keys (not their hashes) and +; vals. +(deftype HashMap [meta count hashobj] + IWithMeta + (-with-meta [coll meta] (HashMap. meta count hashobj)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj + coll + entry))) + + IEmptyableCollection + (-empty [coll] (with-meta cljs.core.HashMap/EMPTY meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] + (when (pos? count) + (let [hashes (.sort (js-keys hashobj))] + (mapcat #(map vec (partition 2 (aget hashobj %))) + hashes)))) + + ICounted + (-count [coll] count) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (let [bucket (aget hashobj (hash k)) + i (when bucket (scan-array 2 k bucket))] + (if i + (aget bucket (inc i)) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [h (hash k) + bucket (aget hashobj h)] + (if bucket + (let [new-bucket (aclone bucket) + new-hashobj (goog.object/clone hashobj)] + (aset new-hashobj h new-bucket) + (if-let [i (scan-array 2 k new-bucket)] + (do ; found key, replace + (aset new-bucket (inc i) v) + (HashMap. meta count new-hashobj)) + (do ; did not find key, append + (.push new-bucket k v) + (HashMap. meta (inc count) new-hashobj)))) + (let [new-hashobj (goog.object/clone hashobj)] ; did not find bucket + (aset new-hashobj h (array k v)) + (HashMap. meta (inc count) new-hashobj))))) + (-contains-key? [coll k] + (let [bucket (aget hashobj (hash k)) + i (when bucket (scan-array 2 k bucket))] + (if i + true + false))) + + IMap + (-dissoc [coll k] + (let [h (hash k) + bucket (aget hashobj h) + i (when bucket (scan-array 2 k bucket))] + (if (not i) + coll ; key not found, return coll unchanged + (let [new-hashobj (goog.object/clone hashobj)] + (if (> 3 (.-length bucket)) + (js-delete new-hashobj h) + (let [new-bucket (aclone bucket)] + (.splice new-bucket i 2) + (aset new-hashobj h new-bucket))) + (HashMap. meta (dec count) new-hashobj))))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj))) + +(set! cljs.core.HashMap/fromArrays (fn [ks vs] + (let [len (.-length ks)] + (loop [i 0, out cljs.core.HashMap/EMPTY] + (if (< i len) + (recur (inc i) (assoc out (aget ks i) (aget vs i))) + out))))) + +(deftype Set [meta hash-map] + IWithMeta + (-with-meta [coll meta] (Set. meta hash-map)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll o] + (Set. meta (assoc hash-map o nil))) + + IEmptyableCollection + (-empty [coll] (with-meta cljs.core.Set/EMPTY meta)) + + IEquiv + (-equiv [coll other] + (and + (set? other) + (= (count coll) (count other)) + (every? #(contains? coll %) + other))) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] (keys hash-map)) + + ICounted + (-count [coll] (count (seq coll))) + + ILookup + (-lookup [coll v] + (-lookup coll v nil)) + (-lookup [coll v not-found] + (if (-contains-key? hash-map v) + v + not-found)) + + ISet + (-disjoin [coll v] + (Set. meta (dissoc hash-map v))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (. Set -EMPTY) (Set. nil (hash-map))) From f9d7b1718e6c750160c3dca6f292bfe3d92b726f Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 18:28:51 -0400 Subject: [PATCH 22/94] - add back printing logic --- src/main/cljs/cljs/core.cljs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index a76f1f191..62415e1ba 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12487,7 +12487,10 @@ reduces them without incurring seq initialization" (-invoke [coll k] (-lookup coll k)) (-invoke [coll k not-found] - (-lookup coll k not-found))) + (-lookup coll k not-found)) + + IPrintWithWriter + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll))) (set! (. Vector -EMPTY) (Vector. nil (array))) @@ -12583,12 +12586,29 @@ reduces them without incurring seq initialization" (-invoke [coll k] (-lookup coll k)) (-invoke [coll k not-found] - (-lookup coll k not-found))) + (-lookup coll k not-found)) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts))) (set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj))) (set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj))) +(defn obj-map + "keyval => key val + Returns a new object map with supplied mappings." + [& keyvals] + (let [ks (array) + obj (js-obj)] + (loop [kvs (seq keyvals)] + (if kvs + (do (.push ks (first kvs)) + (gobject/set obj (first kvs) (second kvs)) + (recur (nnext kvs))) + (.fromObject ObjMap ks obj))))) + ; The keys field is an array of all keys of this map, in no particular ; order. Each key is hashed and the result used as a property name of ; hashobj. Each values in hashobj is actually a bucket in order to handle hash @@ -12681,7 +12701,11 @@ reduces them without incurring seq initialization" (-invoke [coll k] (-lookup coll k)) (-invoke [coll k not-found] - (-lookup coll k not-found))) + (-lookup coll k not-found)) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts))) (set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj))) @@ -12739,6 +12763,9 @@ reduces them without incurring seq initialization" (-invoke [coll k] (-lookup coll k)) (-invoke [coll k not-found] - (-lookup coll k not-found))) + (-lookup coll k not-found)) + + IPrintWithWriter + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll))) (set! (. Set -EMPTY) (Set. nil (hash-map))) From 1f38c967be9c8a19a451bccd3b6583e3768ec8c9 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 19:04:37 -0400 Subject: [PATCH 23/94] remove the deprecated access pattern that clashed w/ namespaces --- src/main/cljs/cljs/core.cljs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 62415e1ba..d04fd38db 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12432,7 +12432,7 @@ reduces them without incurring seq initialization" (Vector. meta new-array))) IEmptyableCollection - (-empty [coll] (with-meta cljs.core.Vector/EMPTY meta)) + (-empty [coll] (with-meta (. Vector -EMPTY) meta)) ISequential IEquiv @@ -12534,7 +12534,7 @@ reduces them without incurring seq initialization" entry))) IEmptyableCollection - (-empty [coll] (with-meta cljs.core.ObjMap/EMPTY meta)) + (-empty [coll] (with-meta (. ObjMap -EMPTY) meta)) IEquiv (-equiv [coll other] (equiv-map coll other)) @@ -12630,7 +12630,7 @@ reduces them without incurring seq initialization" entry))) IEmptyableCollection - (-empty [coll] (with-meta cljs.core.HashMap/EMPTY meta)) + (-empty [coll] (with-meta (. HashMap -EMPTY) meta)) IEquiv (-equiv [coll other] (equiv-map coll other)) @@ -12709,9 +12709,9 @@ reduces them without incurring seq initialization" (set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj))) -(set! cljs.core.HashMap/fromArrays (fn [ks vs] +(set! (. HashMap -fromArrays) (fn [ks vs] (let [len (.-length ks)] - (loop [i 0, out cljs.core.HashMap/EMPTY] + (loop [i 0, out (. HashMap -EMPTY)] (if (< i len) (recur (inc i) (assoc out (aget ks i) (aget vs i))) out))))) @@ -12728,7 +12728,7 @@ reduces them without incurring seq initialization" (Set. meta (assoc hash-map o nil))) IEmptyableCollection - (-empty [coll] (with-meta cljs.core.Set/EMPTY meta)) + (-empty [coll] (with-meta (. Set -EMPTY) meta)) IEquiv (-equiv [coll other] From 9ca79311dd27f8f84a0d197f9ee3eb64fba823ff Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 19:10:14 -0400 Subject: [PATCH 24/94] Revert "remove the deprecated access pattern that clashed w/ namespaces" This reverts commit 1f38c967be9c8a19a451bccd3b6583e3768ec8c9. --- src/main/cljs/cljs/core.cljs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index d04fd38db..62415e1ba 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12432,7 +12432,7 @@ reduces them without incurring seq initialization" (Vector. meta new-array))) IEmptyableCollection - (-empty [coll] (with-meta (. Vector -EMPTY) meta)) + (-empty [coll] (with-meta cljs.core.Vector/EMPTY meta)) ISequential IEquiv @@ -12534,7 +12534,7 @@ reduces them without incurring seq initialization" entry))) IEmptyableCollection - (-empty [coll] (with-meta (. ObjMap -EMPTY) meta)) + (-empty [coll] (with-meta cljs.core.ObjMap/EMPTY meta)) IEquiv (-equiv [coll other] (equiv-map coll other)) @@ -12630,7 +12630,7 @@ reduces them without incurring seq initialization" entry))) IEmptyableCollection - (-empty [coll] (with-meta (. HashMap -EMPTY) meta)) + (-empty [coll] (with-meta cljs.core.HashMap/EMPTY meta)) IEquiv (-equiv [coll other] (equiv-map coll other)) @@ -12709,9 +12709,9 @@ reduces them without incurring seq initialization" (set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj))) -(set! (. HashMap -fromArrays) (fn [ks vs] +(set! cljs.core.HashMap/fromArrays (fn [ks vs] (let [len (.-length ks)] - (loop [i 0, out (. HashMap -EMPTY)] + (loop [i 0, out cljs.core.HashMap/EMPTY] (if (< i len) (recur (inc i) (assoc out (aget ks i) (aget vs i))) out))))) @@ -12728,7 +12728,7 @@ reduces them without incurring seq initialization" (Set. meta (assoc hash-map o nil))) IEmptyableCollection - (-empty [coll] (with-meta (. Set -EMPTY) meta)) + (-empty [coll] (with-meta cljs.core.Set/EMPTY meta)) IEquiv (-equiv [coll other] From 92de06bcf5d1da1e49854dc60f71a74571e97483 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 19:10:22 -0400 Subject: [PATCH 25/94] Revert "- add back printing logic" This reverts commit f9d7b1718e6c750160c3dca6f292bfe3d92b726f. --- src/main/cljs/cljs/core.cljs | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 62415e1ba..a76f1f191 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12487,10 +12487,7 @@ reduces them without incurring seq initialization" (-invoke [coll k] (-lookup coll k)) (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IPrintWithWriter - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll))) + (-lookup coll k not-found))) (set! (. Vector -EMPTY) (Vector. nil (array))) @@ -12586,29 +12583,12 @@ reduces them without incurring seq initialization" (-invoke [coll k] (-lookup coll k)) (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IPrintWithWriter - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts))) + (-lookup coll k not-found))) (set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj))) (set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj))) -(defn obj-map - "keyval => key val - Returns a new object map with supplied mappings." - [& keyvals] - (let [ks (array) - obj (js-obj)] - (loop [kvs (seq keyvals)] - (if kvs - (do (.push ks (first kvs)) - (gobject/set obj (first kvs) (second kvs)) - (recur (nnext kvs))) - (.fromObject ObjMap ks obj))))) - ; The keys field is an array of all keys of this map, in no particular ; order. Each key is hashed and the result used as a property name of ; hashobj. Each values in hashobj is actually a bucket in order to handle hash @@ -12701,11 +12681,7 @@ reduces them without incurring seq initialization" (-invoke [coll k] (-lookup coll k)) (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IPrintWithWriter - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts))) + (-lookup coll k not-found))) (set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj))) @@ -12763,9 +12739,6 @@ reduces them without incurring seq initialization" (-invoke [coll k] (-lookup coll k)) (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IPrintWithWriter - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll))) + (-lookup coll k not-found))) (set! (. Set -EMPTY) (Set. nil (hash-map))) From 8e6fa1ce191ec66c073b8889489ac29050f6c1db Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 19:10:26 -0400 Subject: [PATCH 26/94] Revert "readd the changes" This reverts commit d998f1384cfc2de437f8f4dbec50528f0f08e688. --- src/main/cljs/cljs/core.cljs | 342 ----------------------------------- 1 file changed, 342 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index a76f1f191..4305440a8 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12400,345 +12400,3 @@ reduces them without incurring seq initialization" (identical? "window" *global*) (set! goog/global js/window) (identical? "self" *global*) (set! goog/global js/self) (identical? "global" *global*) (set! goog/global js/global))) - -;; ----------------------------------------------------------------------------- -;; Original 2011 Copy-on-Write Types - -;;; Vector - -(deftype Vector [meta array] - IWithMeta - (-with-meta [coll meta] (Vector. meta array)) - - IMeta - (-meta [coll] meta) - - IStack - (-peek [coll] - (let [count (.-length array)] - (when (> count 0) - (aget array (dec count))))) - (-pop [coll] - (if (> (.-length array) 0) - (let [new-array (aclone array)] - (. new-array (pop)) - (Vector. meta new-array)) - (throw (js/Error. "Can't pop empty vector")))) - - ICollection - (-conj [coll o] - (let [new-array (aclone array)] - (.push new-array o) - (Vector. meta new-array))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.Vector/EMPTY meta)) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeqable - (-seq [coll] - (when (> (.-length array) 0) - (let [vector-seq - (fn vector-seq [i] - (lazy-seq - (when (< i (.-length array)) - (cons (aget array i) (vector-seq (inc i))))))] - (vector-seq 0)))) - - ICounted - (-count [coll] (.-length array)) - - IIndexed - (-nth [coll n] - (if (and (<= 0 n) (< n (.-length array))) - (aget array n) - #_(throw (js/Error. (str "No item " n " in vector of length " (.-length array)))))) - (-nth [coll n not-found] - (if (and (<= 0 n) (< n (.-length array))) - (aget array n) - not-found)) - - ILookup - (-lookup [coll k] (-nth coll k nil)) - (-lookup [coll k not-found] (-nth coll k not-found)) - - IAssociative - (-assoc [coll k v] - (let [new-array (aclone array)] - (aset new-array k v) - (Vector. meta new-array))) - - IVector - (-assoc-n [coll n val] (-assoc coll n val)) - - IReduce - (-reduce [v f] - (ci-reduce array f)) - (-reduce [v f start] - (ci-reduce array f start)) - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! (. Vector -EMPTY) (Vector. nil (array))) - -(set! (. Vector -fromArray) (fn [xs] (Vector. nil xs))) - -; The keys field is an array of all keys of this map, in no particular -; order. Any string, keyword, or symbol key is used as a property name -; to store the value in strobj. If a key is assoc'ed when that same -; key already exists in strobj, the old value is overwritten. If a -; non-string key is assoc'ed, return a HashMap object instead. - -(defn- obj-map-contains-key? - ([k strobj] - (obj-map-contains-key? k strobj true false)) - ([k strobj true-val false-val] - (if (and (goog/isString k) (.hasOwnProperty strobj k)) - true-val - false-val))) - -(defn- obj-map-compare-keys [a b] - (let [a (hash a) - b (hash b)] - (cond - (< a b) -1 - (> a b) 1 - :else 0))) - -(deftype ObjMap [meta keys strobj] - IWithMeta - (-with-meta [coll meta] (ObjMap. meta keys strobj)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.ObjMap/EMPTY meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeqable - (-seq [coll] - (when (pos? (.-length keys)) - (map #(vector % (aget strobj %)) - (.sort keys obj-map-compare-keys)))) - - ICounted - (-count [coll] (.-length keys)) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] - (obj-map-contains-key? k strobj (aget strobj k) not-found)) - - IAssociative - (-assoc [coll k v] - (if (goog/isString k) - (let [new-strobj (goog.object/clone strobj) - overwrite? (.hasOwnProperty new-strobj k)] - (aset new-strobj k v) - (if overwrite? - (ObjMap. meta keys new-strobj) ; overwrite - (let [new-keys (aclone keys)] ; append - (.push new-keys k) - (ObjMap. meta new-keys new-strobj)))) - ; non-string key. game over. - (with-meta (into (hash-map k v) (seq coll)) meta))) - (-contains-key? [coll k] - (obj-map-contains-key? k strobj)) - - IMap - (-dissoc [coll k] - (if (and (goog/isString k) (.hasOwnProperty strobj k)) - (let [new-keys (aclone keys) - new-strobj (goog.object/clone strobj)] - (.splice new-keys (scan-array 1 k new-keys) 1) - (js-delete new-strobj k) - (ObjMap. meta new-keys new-strobj)) - coll)) ; key not found, return coll unchanged - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj))) - -(set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj))) - -; The keys field is an array of all keys of this map, in no particular -; order. Each key is hashed and the result used as a property name of -; hashobj. Each values in hashobj is actually a bucket in order to handle hash -; collisions. A bucket is an array of alternating keys (not their hashes) and -; vals. -(deftype HashMap [meta count hashobj] - IWithMeta - (-with-meta [coll meta] (HashMap. meta count hashobj)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.HashMap/EMPTY meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeqable - (-seq [coll] - (when (pos? count) - (let [hashes (.sort (js-keys hashobj))] - (mapcat #(map vec (partition 2 (aget hashobj %))) - hashes)))) - - ICounted - (-count [coll] count) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] - (let [bucket (aget hashobj (hash k)) - i (when bucket (scan-array 2 k bucket))] - (if i - (aget bucket (inc i)) - not-found))) - - IAssociative - (-assoc [coll k v] - (let [h (hash k) - bucket (aget hashobj h)] - (if bucket - (let [new-bucket (aclone bucket) - new-hashobj (goog.object/clone hashobj)] - (aset new-hashobj h new-bucket) - (if-let [i (scan-array 2 k new-bucket)] - (do ; found key, replace - (aset new-bucket (inc i) v) - (HashMap. meta count new-hashobj)) - (do ; did not find key, append - (.push new-bucket k v) - (HashMap. meta (inc count) new-hashobj)))) - (let [new-hashobj (goog.object/clone hashobj)] ; did not find bucket - (aset new-hashobj h (array k v)) - (HashMap. meta (inc count) new-hashobj))))) - (-contains-key? [coll k] - (let [bucket (aget hashobj (hash k)) - i (when bucket (scan-array 2 k bucket))] - (if i - true - false))) - - IMap - (-dissoc [coll k] - (let [h (hash k) - bucket (aget hashobj h) - i (when bucket (scan-array 2 k bucket))] - (if (not i) - coll ; key not found, return coll unchanged - (let [new-hashobj (goog.object/clone hashobj)] - (if (> 3 (.-length bucket)) - (js-delete new-hashobj h) - (let [new-bucket (aclone bucket)] - (.splice new-bucket i 2) - (aset new-hashobj h new-bucket))) - (HashMap. meta (dec count) new-hashobj))))) - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj))) - -(set! cljs.core.HashMap/fromArrays (fn [ks vs] - (let [len (.-length ks)] - (loop [i 0, out cljs.core.HashMap/EMPTY] - (if (< i len) - (recur (inc i) (assoc out (aget ks i) (aget vs i))) - out))))) - -(deftype Set [meta hash-map] - IWithMeta - (-with-meta [coll meta] (Set. meta hash-map)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll o] - (Set. meta (assoc hash-map o nil))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.Set/EMPTY meta)) - - IEquiv - (-equiv [coll other] - (and - (set? other) - (= (count coll) (count other)) - (every? #(contains? coll %) - other))) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeqable - (-seq [coll] (keys hash-map)) - - ICounted - (-count [coll] (count (seq coll))) - - ILookup - (-lookup [coll v] - (-lookup coll v nil)) - (-lookup [coll v not-found] - (if (-contains-key? hash-map v) - v - not-found)) - - ISet - (-disjoin [coll v] - (Set. meta (dissoc hash-map v))) - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! (. Set -EMPTY) (Set. nil (hash-map))) From 0277a7566215e177dd9edf37a2572c1e99423a71 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Tue, 5 Aug 2025 21:41:43 -0400 Subject: [PATCH 27/94] break out chunked-seq, es6-iterator, and interop .equiv test from cljs.core-test (#266) --- src/test/cljs/cljs/chunked_seq.cljs | 27 ++++++++++ src/test/cljs/cljs/core_test.cljs | 75 ---------------------------- src/test/cljs/cljs/interop_test.cljs | 70 ++++++++++++++++++++++++++ src/test/cljs/test_runner.cljs | 4 ++ 4 files changed, 101 insertions(+), 75 deletions(-) create mode 100644 src/test/cljs/cljs/chunked_seq.cljs create mode 100644 src/test/cljs/cljs/interop_test.cljs diff --git a/src/test/cljs/cljs/chunked_seq.cljs b/src/test/cljs/cljs/chunked_seq.cljs new file mode 100644 index 000000000..73abf707f --- /dev/null +++ b/src/test/cljs/cljs/chunked_seq.cljs @@ -0,0 +1,27 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.chunked-seq + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is are]])) + +(deftest test-cljs-2693 + (is (chunked-seq? (range 5))) + (is (satisfies? IChunk (chunk-first (range 5)))) + (is (nil? (chunk-next (range 32)))) + (is (not (chunked-seq? (range 2 -2 0)))) + (is (chunked-seq? (range))) + (is (= 5 (count (chunk-first (range 5))))) + (is (= 32 (count (chunk-first (range))))) + (is (= 17 (nth (chunk-first (range 100)) 17))) + (is (= 35 (nth (chunk-first (range 100)) 35))) + (is (= 32 (count (chunk-first (range 100))))) + (is (= 0 (first (range 5)))) + (is (= 1 (second (range 5)))) + (is (= (range 1 5) (rest (range 5)))) + (is (= (range 1 5) (next (range 5))))) \ No newline at end of file diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index 9d9b4306e..fca3dd11e 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -408,17 +408,6 @@ (halt-when :anomaly #(assoc %2 :partial-results %1)) [1 2 {:anomaly :oh-no!} 3 4])))) -(deftest test-obj-equiv - (testing "Object equiv method" - (is (.equiv :foo :foo)) - (is (.equiv 'foo 'foo)) - (is (.equiv {:foo 1 :bar 2} {:foo 1 :bar 2})) - (is (.equiv [1 2 3] [1 2 3])) - (is (.equiv '(1 2 3) '(1 2 3))) - (is (.equiv (map inc [1 2 3]) (map inc [1 2 3]))) - (is (.equiv #{:cat :dog :bird} #{:cat :dog :bird})) - )) - (defn seq-iter-match [coll] (let [i (-iterator coll)] @@ -456,54 +445,6 @@ (is (= true (seq-iter-match test-queue))) (is (= true (seq-iter-match test-record)))))) -(deftest test-es6-interfaces - (testing "ES6 collection interfaces" - (let [iter (es6-iterator [1 2 3])] - (testing "basic iterations" - (is (= (.-value (.next iter)) 1)) - (is (= (.-value (.next iter)) 2)) - (is (= (.-value (.next iter)) 3)) - (is (.-done (.next iter))))) - (is (.has {:foo "bar"} :foo)) - (is (= (.get {:foo "bar"} :foo) "bar")) - (is (= (.get {:foo "bar"} :bar :default) :default)) - (let [iter (.keys {:foo "bar" :baz "woz"})] - (testing "map key iteration" - (is (#{:foo :baz} (.-value (.next iter)))) - (is (#{:foo :baz} (.-value (.next iter)))) - (is (.-done (.next iter))))) - (let [eiter (.entries {:foo "bar" :baz "woz"})] - (testing "map entry iteration" - (let [entries #{(seq #js [:foo "bar"]) (seq #js [:baz "woz"])}] - (is (entries (seq (.-value (.next eiter))))) - (is (entries (seq (.-value (.next eiter)))))) - (is (.-done (.next eiter))))) - (let [iter (.values {:foo "bar" :baz "woz"})] - (testing "map value iteration" - (is (#{"bar" "woz"} (.-value (.next iter)))) - (is (#{"bar" "woz"} (.-value (.next iter)))) - (is (.-done (.next iter))))) - (is (.has #{:cat :bird :dog} :bird)) - (let [iter (.keys #{:cat :bird :dog})] - (testing "set key iteration" - (is (#{:cat :bird :dog} (.-value (.next iter)))) - (is (#{:cat :bird :dog} (.-value (.next iter)))) - (is (#{:cat :bird :dog} (.-value (.next iter)))) - (is (.-done (.next iter))))) - (let [iter (.entries #{:cat :bird :dog})] - (testing "set entry iteration" - (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) - (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) - (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) - (is (.-done (.next iter))))) - (let [iter (.values #{:cat :bird :dog})] - (testing "set value iteration" - (is (#{:cat :bird :dog} (.-value (.next iter)))) - (is (#{:cat :bird :dog} (.-value (.next iter)))) - (is (#{:cat :bird :dog} (.-value (.next iter)))) - (is (.-done (.next iter))))) -)) - (deftest test-reader-literals (testing "Testing reader literals" (is (= #queue [1] (into cljs.core.PersistentQueue.EMPTY [1]))) @@ -1670,22 +1611,6 @@ ;; Make sure we didn't delete the alpha? fn (is (some? alpha-2585?))) -(deftest test-cljs-2693 - (is (chunked-seq? (range 5))) - (is (satisfies? IChunk (chunk-first (range 5)))) - (is (nil? (chunk-next (range 32)))) - (is (not (chunked-seq? (range 2 -2 0)))) - (is (chunked-seq? (range))) - (is (= 5 (count (chunk-first (range 5))))) - (is (= 32 (count (chunk-first (range))))) - (is (= 17 (nth (chunk-first (range 100)) 17))) - (is (= 35 (nth (chunk-first (range 100)) 35))) - (is (= 32 (count (chunk-first (range 100))))) - (is (= 0 (first (range 5)))) - (is (= 1 (second (range 5)))) - (is (= (range 1 5) (rest (range 5)))) - (is (= (range 1 5) (next (range 5))))) - (defn fn-2741* ([x]) ([x y])) (def fn-2741 fn-2741*) diff --git a/src/test/cljs/cljs/interop_test.cljs b/src/test/cljs/cljs/interop_test.cljs new file mode 100644 index 000000000..ee158ec18 --- /dev/null +++ b/src/test/cljs/cljs/interop_test.cljs @@ -0,0 +1,70 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.interop-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is are]])) + +(deftest test-obj-equiv + (testing "Object equiv method" + (is (.equiv :foo :foo)) + (is (.equiv 'foo 'foo)) + (is (.equiv {:foo 1 :bar 2} {:foo 1 :bar 2})) + (is (.equiv [1 2 3] [1 2 3])) + (is (.equiv '(1 2 3) '(1 2 3))) + (is (.equiv (map inc [1 2 3]) (map inc [1 2 3]))) + (is (.equiv #{:cat :dog :bird} #{:cat :dog :bird})) + )) + +(deftest test-es6-interfaces + (testing "ES6 collection interfaces" + (let [iter (es6-iterator [1 2 3])] + (testing "basic iterations" + (is (= (.-value (.next iter)) 1)) + (is (= (.-value (.next iter)) 2)) + (is (= (.-value (.next iter)) 3)) + (is (.-done (.next iter))))) + (is (.has {:foo "bar"} :foo)) + (is (= (.get {:foo "bar"} :foo) "bar")) + (is (= (.get {:foo "bar"} :bar :default) :default)) + (let [iter (.keys {:foo "bar" :baz "woz"})] + (testing "map key iteration" + (is (#{:foo :baz} (.-value (.next iter)))) + (is (#{:foo :baz} (.-value (.next iter)))) + (is (.-done (.next iter))))) + (let [eiter (.entries {:foo "bar" :baz "woz"})] + (testing "map entry iteration" + (let [entries #{(seq #js [:foo "bar"]) (seq #js [:baz "woz"])}] + (is (entries (seq (.-value (.next eiter))))) + (is (entries (seq (.-value (.next eiter)))))) + (is (.-done (.next eiter))))) + (let [iter (.values {:foo "bar" :baz "woz"})] + (testing "map value iteration" + (is (#{"bar" "woz"} (.-value (.next iter)))) + (is (#{"bar" "woz"} (.-value (.next iter)))) + (is (.-done (.next iter))))) + (is (.has #{:cat :bird :dog} :bird)) + (let [iter (.keys #{:cat :bird :dog})] + (testing "set key iteration" + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (.-done (.next iter))))) + (let [iter (.entries #{:cat :bird :dog})] + (testing "set entry iteration" + (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) + (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) + (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) + (is (.-done (.next iter))))) + (let [iter (.values #{:cat :bird :dog})] + (testing "set value iteration" + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (.-done (.next iter))))) + )) \ No newline at end of file diff --git a/src/test/cljs/test_runner.cljs b/src/test/cljs/test_runner.cljs index a0e411309..10abbdf54 100644 --- a/src/test/cljs/test_runner.cljs +++ b/src/test/cljs/test_runner.cljs @@ -17,6 +17,8 @@ [cljs.collections-test] [cljs.hashing-test] [cljs.core-test :as core-test] + [cljs.chunked-seq] + [cljs.interop-test] [cljs.reader-test] [cljs.binding-test] [cljs.parse-test] @@ -77,6 +79,8 @@ 'cljs.collections-test 'cljs.hashing-test 'cljs.core-test + 'cljs.chunked-seq + 'cljs.interop-test 'cljs.reader-test 'cljs.parse-test 'clojure.set-test From ddf3cfe14e71b342fbe29ff858c620752ccfc4cb Mon Sep 17 00:00:00 2001 From: David Nolen Date: Wed, 6 Aug 2025 07:04:58 -0400 Subject: [PATCH 28/94] Break out iterator tests from cljs.core-test (#268) --- src/test/cljs/cljs/core_test.cljs | 37 --------------------- src/test/cljs/cljs/iterator_test.cljs | 47 +++++++++++++++++++++++++++ src/test/cljs/test_runner.cljs | 2 ++ 3 files changed, 49 insertions(+), 37 deletions(-) create mode 100644 src/test/cljs/cljs/iterator_test.cljs diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index fca3dd11e..686a0fd72 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -408,43 +408,6 @@ (halt-when :anomaly #(assoc %2 :partial-results %1)) [1 2 {:anomaly :oh-no!} 3 4])))) -(defn seq-iter-match - [coll] - (let [i (-iterator coll)] - (loop [s (seq coll) - n 0] - (if (seq s) - (do - (when-not (.hasNext i) - (throw - (js/Error. - (str "Iterator exhausted before seq at(" n ")" )))) - (let [iv (.next i) - sv (first s)] - (when-not (= iv sv) - (throw - (js/Error. - (str "Iterator value " iv " and seq value " sv " did not match at ( " n ")"))))) - (recur (rest s) (inc n))) - (if (.hasNext i) - (throw - (js/Error. - (str "Seq exhausted before iterator at (" n ")"))) - true))))) - -(defrecord TestIterRec [a b]) - -(deftest coll-iter-seq-match - (testing "Direct iterators match sequences" - (let [test-map (apply hash-map (range 200)) - test-set (apply hash-set (range 200)) - test-queue (into cljs.core.PersistentQueue.EMPTY (vec (range 100))) - test-record (into (TestIterRec. 1 2) {:c 3 :d 4})] - (is (= true (seq-iter-match test-map))) - (is (= true (seq-iter-match test-set))) - (is (= true (seq-iter-match test-queue))) - (is (= true (seq-iter-match test-record)))))) - (deftest test-reader-literals (testing "Testing reader literals" (is (= #queue [1] (into cljs.core.PersistentQueue.EMPTY [1]))) diff --git a/src/test/cljs/cljs/iterator_test.cljs b/src/test/cljs/cljs/iterator_test.cljs new file mode 100644 index 000000000..84e9e6a4d --- /dev/null +++ b/src/test/cljs/cljs/iterator_test.cljs @@ -0,0 +1,47 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.iterator-test + (:require [cljs.test :refer-macros [deftest testing is are run-tests]])) + +(defn seq-iter-match + [coll] + (let [i (-iterator coll)] + (loop [s (seq coll) + n 0] + (if (seq s) + (do + (when-not (.hasNext i) + (throw + (js/Error. + (str "Iterator exhausted before seq at(" n ")" )))) + (let [iv (.next i) + sv (first s)] + (when-not (= iv sv) + (throw + (js/Error. + (str "Iterator value " iv " and seq value " sv " did not match at ( " n ")"))))) + (recur (rest s) (inc n))) + (if (.hasNext i) + (throw + (js/Error. + (str "Seq exhausted before iterator at (" n ")"))) + true))))) + +(defrecord TestIterRec [a b]) + +(deftest coll-iter-seq-match + (testing "Direct iterators match sequences" + (let [test-map (apply hash-map (range 200)) + test-set (apply hash-set (range 200)) + test-queue (into cljs.core.PersistentQueue.EMPTY (vec (range 100))) + test-record (into (TestIterRec. 1 2) {:c 3 :d 4})] + (is (= true (seq-iter-match test-map))) + (is (= true (seq-iter-match test-set))) + (is (= true (seq-iter-match test-queue))) + (is (= true (seq-iter-match test-record)))))) \ No newline at end of file diff --git a/src/test/cljs/test_runner.cljs b/src/test/cljs/test_runner.cljs index 10abbdf54..7e551f9de 100644 --- a/src/test/cljs/test_runner.cljs +++ b/src/test/cljs/test_runner.cljs @@ -19,6 +19,7 @@ [cljs.core-test :as core-test] [cljs.chunked-seq] [cljs.interop-test] + [cljs.iterator-test] [cljs.reader-test] [cljs.binding-test] [cljs.parse-test] @@ -81,6 +82,7 @@ 'cljs.core-test 'cljs.chunked-seq 'cljs.interop-test + 'cljs.iterator-test 'cljs.reader-test 'cljs.parse-test 'clojure.set-test From f9a6856d91e45377391406fc34a581bc4043615e Mon Sep 17 00:00:00 2001 From: David Nolen Date: Fri, 3 Oct 2025 22:05:31 -0400 Subject: [PATCH 29/94] Experimental Lite Mode (#265) new code-size compiler flags: :lite-mode, :elide-to-string :lite-mode emits updated variants of the original copy-on-write data structures from 2011. Notably, in `:lite-mode` chunked-seqs are not supported as they pull in a significant amount of code, which defeats the purpose of the new flags. Also while the transient interfaces are implemented on the copy-on-write data structures, they do not actually provide a fast path. This is not by design, but simply a practical scoping of effort as the work done here is already considerable. The other serious code-size issue are `.toString` impls. There are many interesting smaller programs that simply do not need the convenient `.toString` invoking the ClojureScript recursive printing machinery. By combining `:lite-mode true` with `:elide-to-string true` the emitted code is cut in half or more for simpler programs. For a rough idea of the space-savings, a hash-map literal is ~6K after advanced compilation with the new flags and brotli compression. ClojureScript 1.12.42 is ~17K brotli for the same program. The following expression (.log js/console (->> (map inc (range 10)) (filter even?) (partition 2) (drop 1) (mapcat identity) into-array)) Is ~6K after advanced compilation + brotli compression. ClojureScript is 1.12.42 is ~19K. For users that want the full expressiveness of the Clojure APIs and the interactivity of the REPL for smaller and/or simpler projects, but don't need every last bit of performance, these two flags should lead to compact artifacts. --- .github/workflows/test.yaml | 57 + deps.edn | 2 + resources/lite_test.edn | 28 + src/main/cljs/cljs/analyzer/passes/lite.cljc | 32 + src/main/cljs/cljs/core.cljs | 1036 +++++++++++++---- src/main/cljs/cljs/spec/alpha.cljs | 4 + src/main/clojure/cljs/analyzer.cljc | 20 +- src/main/clojure/cljs/closure.clj | 7 +- src/main/clojure/cljs/compiler.cljc | 48 +- src/main/clojure/cljs/core.cljc | 19 +- src/test/cljs/cljs/collections_test.cljs | 81 +- src/test/cljs/cljs/interop_test.cljs | 13 +- src/test/cljs/cljs/lite_collections_test.cljs | 32 + src/test/cljs/cljs/metadata_test.cljc | 12 +- src/test/cljs/cljs/seqs_test.cljs | 35 +- src/test/cljs/cljs/walk_test.cljs | 45 +- src/test/cljs/lite_test_runner.cljs | 127 ++ src/test/cljs_build/trivial/core6.cljs | 3 + src/test/clojure/cljs/analyzer_pass_tests.clj | 19 + src/test/clojure/cljs/build_api_tests.clj | 50 +- 20 files changed, 1391 insertions(+), 279 deletions(-) create mode 100644 resources/lite_test.edn create mode 100644 src/main/cljs/cljs/analyzer/passes/lite.cljc create mode 100644 src/test/cljs/cljs/lite_collections_test.cljs create mode 100644 src/test/cljs/lite_test_runner.cljs create mode 100644 src/test/cljs_build/trivial/core6.cljs diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e98aa8818..4420e9d0b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -59,6 +59,63 @@ jobs: /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/jsc builds/out-adv/core-advanced-test.js | tee test-out.txt grep -qxF '0 failures, 0 errors.' test-out.txt + # Lite Tests + lite-test: + name: Lite Tests + runs-on: macos-14 + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.1 + with: + tools-deps: '1.10.1.763' + + - name: Cache maven + uses: actions/cache@v4 + env: + cache-name: cache-maven + with: + path: ~/.m2 + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Cache gitlibs + uses: actions/cache@v4 + env: + cache-name: cache-gitlibs + with: + path: ~/.gitlibs + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + # - name: Cache JSC + # uses: actions/cache@v4 + # env: + # cache-name: cache-jsc + # with: + # path: WebKit + # key: ${{ runner.os }}-jsc + # restore-keys: | + # ${{ runner.os }}-jsc + + - name: Build tests + run: clojure -M:lite.test.build + + # - name: Install JSC + # run: ./ci/install_jsc.sh + + - name: Run tests + run: | + /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/jsc builds/out-lite/lite-test.js | tee test-out.txt + grep -qxF '0 failures, 0 errors.' test-out.txt + # Runtime Tests runtime-windows-test: name: Runtime Windows Tests diff --git a/deps.edn b/deps.edn index 012e22069..e3a236c2a 100644 --- a/deps.edn +++ b/deps.edn @@ -19,6 +19,8 @@ "-e" "(cljs.test-runner/-main)"]} :runtime.test.build {:extra-paths ["src/test/cljs"] :main-opts ["-m" "cljs.main" "-co" "resources/test.edn" "-c"]} + :lite.test.build {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-co" "resources/lite_test.edn" "-c"]} :selfhost.test.build {:extra-paths ["src/test/self"] :main-opts ["-m" "cljs.main" "-co" "resources/self_host_test.edn" "-c"]} :selfparity.test.build {:extra-paths ["src/test/self"] diff --git a/resources/lite_test.edn b/resources/lite_test.edn new file mode 100644 index 000000000..44508575d --- /dev/null +++ b/resources/lite_test.edn @@ -0,0 +1,28 @@ +{:optimizations :advanced + :main lite-test-runner + :output-to "builds/out-lite/lite-test.js" + :output-dir "builds/out-lite" + :output-wrapper true + :verbose true + :compiler-stats true + :parallel-build true + :npm-deps {:lodash "4.17.4"} + :closure-warnings {:non-standard-jsdoc :off :global-this :off} + :install-deps true + :language-out :es5 + :foreign-libs + [{:file "src/test/cljs/calculator_global.js" + :provides ["calculator"] + :global-exports {calculator Calculator}} + {:file "src/test/cljs/es6_dep.js" + :module-type :es6 + :provides ["es6_calc"]} + {:file "src/test/cljs/calculator.js" + :module-type :commonjs + :provides ["calculator"]} + {:file "src/test/cljs/es6_default_hello.js" + :provides ["es6_default_hello"] + :module-type :es6}] + :pseudo-names true + :pretty-print true + :lite-mode true} diff --git a/src/main/cljs/cljs/analyzer/passes/lite.cljc b/src/main/cljs/cljs/analyzer/passes/lite.cljc new file mode 100644 index 000000000..08a7e03de --- /dev/null +++ b/src/main/cljs/cljs/analyzer/passes/lite.cljc @@ -0,0 +1,32 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.passes.lite + (:refer-clojure :exclude [var?])) + +(defn var? [ast] + (= :var (:op ast))) + +(def ctor->simple-ctor + '{cljs.core/vector cljs.core/simple-vector + cljs.core/vec cljs.core/simple-vec}) + +(defn update-var [{:keys [name] :as ast}] + (let [replacement (get ctor->simple-ctor name)] + (-> ast + (assoc :name replacement) + (assoc-in [:info :name] replacement)))) + +(defn replace-var? [ast] + (and (var? ast) + (contains? ctor->simple-ctor (:name ast)))) + +(defn use-lite-types + [env ast _] + (cond-> ast + (replace-var? ast) update-var)) \ No newline at end of file diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 4305440a8..8eabe774b 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -53,6 +53,11 @@ , and \"global\" supported. "} *global* "default") +(goog-define + ^{:doc "Boolean flag for LITE_MODE" + :jsdoc ["@type {boolean}"]} + LITE_MODE false) + (def ^{:dynamic true :doc "Var bound to the current namespace. Only used for bootstrapping." @@ -1763,7 +1768,7 @@ reduces them without incurring seq initialization" (indexOf [coll x start] (-indexOf coll x start)) (lastIndexOf [coll x] - (-lastIndexOf coll x (count coll))) + (-lastIndexOf coll x (-count coll))) (lastIndexOf [coll x start] (-lastIndexOf coll x start)) @@ -2070,7 +2075,7 @@ reduces them without incurring seq initialization" (-assoc coll k v) (if-not (nil? coll) (-assoc coll k v) - (array-map k v)))) + {k v}))) ([coll k v & kvs] (let [ret (assoc coll k v)] (if kvs @@ -2262,7 +2267,10 @@ reduces them without incurring seq initialization" (defn chunked-seq? "Return true if x satisfies IChunkedSeq." - [x] (implements? IChunkedSeq x)) + [x] + (if-not ^boolean LITE_MODE + (implements? IChunkedSeq x) + false)) ;;;;;;;;;;;;;;;;;;;; js primitives ;;;;;;;;;;;; (defn js-obj @@ -3606,7 +3614,13 @@ reduces them without incurring seq initialization" (-conj [coll o] (cons o coll)) IEmptyableCollection - (-empty [coll] (-with-meta (.-EMPTY List) meta)) + (-empty [coll] + ;; MAYBE FIXME: :lite-mode testing uncovered a very old bug, empty on seq + ;; should discared the metadata, we change the behavior in LITE_MODE for now + ;; to avoid a breaking change + (if-not ^boolean LITE_MODE + (-with-meta (.-EMPTY List) meta) + (.-EMPTY List))) ISequential IEquiv @@ -6520,7 +6534,7 @@ reduces them without incurring seq initialization" ICounted (-count [coll] count)) -(set! (.-EMPTY PersistentQueue) (PersistentQueue. nil 0 nil [] empty-ordered-hash)) +(set! (.-EMPTY PersistentQueue) (PersistentQueue. nil 0 nil (.-EMPTY PersistentVector) empty-ordered-hash)) (es6-iterable PersistentQueue) @@ -6552,172 +6566,6 @@ reduces them without incurring seq initialization" (= (get y (first xkv) never-equiv) (second xkv))) x)))))) - -(defn- scan-array [incr k array] - (let [len (alength array)] - (loop [i 0] - (when (< i len) - (if (identical? k (aget array i)) - i - (recur (+ i incr))))))) - -; The keys field is an array of all keys of this map, in no particular -; order. Any string, keyword, or symbol key is used as a property name -; to store the value in strobj. If a key is assoc'ed when that same -; key already exists in strobj, the old value is overwritten. If a -; non-string key is assoc'ed, return a HashMap object instead. - -(defn- obj-map-compare-keys [a b] - (let [a (hash a) - b (hash b)] - (cond - (< a b) -1 - (> a b) 1 - :else 0))) - -(defn- obj-map->hash-map [m k v] - (let [ks (.-keys m) - len (alength ks) - so (.-strobj m) - mm (meta m)] - (loop [i 0 - out (transient (.-EMPTY PersistentHashMap))] - (if (< i len) - (let [k (aget ks i)] - (recur (inc i) (assoc! out k (gobject/get so k)))) - (-with-meta (persistent! (assoc! out k v)) mm))))) - -;;; ObjMap - DEPRECATED - -(defn- obj-clone [obj ks] - (let [new-obj (js-obj) - l (alength ks)] - (loop [i 0] - (when (< i l) - (let [k (aget ks i)] - (gobject/set new-obj k (gobject/get obj k)) - (recur (inc i))))) - new-obj)) - -(deftype ObjMap [meta keys strobj update-count ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - (equiv [this other] - (-equiv this other)) - - IWithMeta - (-with-meta [coll new-meta] - (if (identical? new-meta meta) - coll - (ObjMap. new-meta keys strobj update-count __hash))) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (-with-meta (.-EMPTY ObjMap) meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) - - ISeqable - (-seq [coll] - (when (pos? (alength keys)) - (map #(vector % (unchecked-get strobj %)) - (.sort keys obj-map-compare-keys)))) - - ICounted - (-count [coll] (alength keys)) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (unchecked-get strobj k) - not-found)) - - IAssociative - (-assoc [coll k v] - (if (string? k) - (if (or (> update-count (.-HASHMAP_THRESHOLD ObjMap)) - (>= (alength keys) (.-HASHMAP_THRESHOLD ObjMap))) - (obj-map->hash-map coll k v) - (if-not (nil? (scan-array 1 k keys)) - (let [new-strobj (obj-clone strobj keys)] - (gobject/set new-strobj k v) - (ObjMap. meta keys new-strobj (inc update-count) nil)) ; overwrite - (let [new-strobj (obj-clone strobj keys) ; append - new-keys (aclone keys)] - (gobject/set new-strobj k v) - (.push new-keys k) - (ObjMap. meta new-keys new-strobj (inc update-count) nil)))) - ;; non-string key. game over. - (obj-map->hash-map coll k v))) - (-contains-key? [coll k] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - true - false)) - - IFind - (-find [coll k] - (when (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (MapEntry. k (unchecked-get strobj k) nil))) - - IKVReduce - (-kv-reduce [coll f init] - (let [len (alength keys)] - (loop [keys (.sort keys obj-map-compare-keys) - init init] - (if (seq keys) - (let [k (first keys) - init (f init k (unchecked-get strobj k))] - (if (reduced? init) - @init - (recur (rest keys) init))) - init)))) - - IMap - (-dissoc [coll k] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (let [new-keys (aclone keys) - new-strobj (obj-clone strobj keys)] - (.splice new-keys (scan-array 1 k new-keys) 1) - (js-delete new-strobj k) - (ObjMap. meta new-keys new-strobj (inc update-count) nil)) - coll)) ; key not found, return coll unchanged - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IEditableCollection - (-as-transient [coll] - (transient (into (hash-map) coll)))) - -(set! (.-EMPTY ObjMap) (ObjMap. nil (array) (js-obj) 0 empty-unordered-hash)) - -(set! (.-HASHMAP_THRESHOLD ObjMap) 8) - -(set! (.-fromObject ObjMap) (fn [ks obj] (ObjMap. nil ks obj 0 nil))) - ;; Record Iterator (deftype RecordIter [^:mutable i record base-count fields ext-map-iter] Object @@ -9191,19 +9039,6 @@ reduces them without incurring seq initialization" (.createAsIfByAssoc PersistentArrayMap (to-array s)) (if (seq s) (first s) (.-EMPTY PersistentArrayMap)))) -(defn obj-map - "keyval => key val - Returns a new object map with supplied mappings." - [& keyvals] - (let [ks (array) - obj (js-obj)] - (loop [kvs (seq keyvals)] - (if kvs - (do (.push ks (first kvs)) - (gobject/set obj (first kvs) (second kvs)) - (recur (nnext kvs))) - (.fromObject ObjMap ks obj))))) - (defn sorted-map "keyval => key val Returns a new sorted map with supplied mappings." @@ -10509,8 +10344,10 @@ reduces them without incurring seq initialization" (-write writer end))))) (defn write-all [writer & ss] - (doseq [s ss] - (-write writer s))) + (loop [ss (seq ss)] + (when-not (nil? ss) + (-write writer (first ss)) + (recur (next ss))))) (defn string-print [x] (when (nil? *print-fn*) @@ -10545,13 +10382,7 @@ reduces them without incurring seq initialization" (implements? IMeta obj) (not (nil? (meta obj))))) -(defn- pr-map-entry [k v] - (reify - IMapEntry - (-key [_] k) - (-val [_] v) - ISeqable - (-seq [_] (IndexedSeq. #js [k v] 0 nil)))) +(declare Vector) (defn- pr-writer-impl [obj writer opts] @@ -10591,9 +10422,10 @@ reduces them without incurring seq initialization" (.map (js-keys obj) (fn [k] - (pr-map-entry + (MapEntry. (cond-> k (some? (.match k #"^[A-Za-z_\*\+\?!\-'][\w\*\+\?!\-']*$")) keyword) - (unchecked-get obj k)))) + (unchecked-get obj k) + nil))) pr-writer writer opts)) (array? obj) @@ -10660,9 +10492,11 @@ reduces them without incurring seq initialization" (defn pr-seq-writer [objs writer opts] (pr-writer (first objs) writer opts) - (doseq [obj (next objs)] - (-write writer " ") - (pr-writer obj writer opts))) + (loop [objs (next objs)] + (when-not (nil? objs) + (-write writer " ") + (pr-writer (first objs) writer opts) + (recur (next objs))))) (defn- pr-sb-with-opts [objs opts] (let [sb (StringBuffer.) @@ -10772,10 +10606,10 @@ reduces them without incurring seq initialization" (when (or (keyword? k) (symbol? k)) (if ns (when (= ns (namespace k)) - (.push lm (pr-map-entry (strip-ns k) v)) + (.push lm (MapEntry. (strip-ns k) v nil)) (recur ns entries)) (when-let [new-ns (namespace k)] - (.push lm (pr-map-entry (strip-ns k) v)) + (.push lm (MapEntry. (strip-ns k) v nil)) (recur new-ns entries)))) #js [ns lm]))))) @@ -10855,10 +10689,6 @@ reduces them without incurring seq initialization" MapEntry (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) - ObjMap - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts)) - KeySeq (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) @@ -12400,3 +12230,801 @@ reduces them without incurring seq initialization" (identical? "window" *global*) (set! goog/global js/window) (identical? "self" *global*) (set! goog/global js/self) (identical? "global" *global*) (set! goog/global js/global))) + +;; ----------------------------------------------------------------------------- +;; Original 2011 Copy-on-Write Types + +;;; Vector + +(deftype VectorIterator [arr ^:mutable i] + Object + (hasNext [_] + (< i (alength arr))) + (next [_] + (let [x (aget arr i)] + (set! i (inc i)) + x))) + +(deftype Vector [meta array ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [coll other] + (-equiv coll other)) + (indexOf [coll x start] + (let [start (if (nil? start) 0 start) + len (-count coll)] + (if (>= start len) + -1 + (loop [idx (cond + (pos? start) start + (neg? start) (max 0 (+ start len)) + :else start)] + (if (< idx len) + (if (= (-nth coll idx) x) + idx + (recur (inc idx))) + -1))))) + (lastIndexOf [coll x start] + (let [start (if (nil? start) (alength array) start) + len (-count coll)] + (if (zero? len) + -1 + (loop [idx (cond + (pos? start) (min (dec len) start) + (neg? start) (+ len start) + :else start)] + (if (>= idx 0) + (if (= (-nth coll idx) x) + idx + (recur (dec idx))) + -1))))) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (Vector. new-meta array __hash))) + + ICloneable + (-clone [coll] (Vector. meta array __hash)) + + IMeta + (-meta [coll] meta) + + IStack + (-peek [coll] + (let [count (alength array)] + (when (> count 0) + (aget array (dec count))))) + (-pop [coll] + (if (> (alength array) 0) + (let [new-array (aclone array)] + (. new-array (pop)) + (Vector. meta new-array nil)) + (throw (js/Error. "Can't pop empty vector")))) + + ICollection + (-conj [coll o] + (let [new-array (aclone array)] + (.push new-array o) + (Vector. meta new-array nil))) + + IEmptyableCollection + (-empty [coll] (with-meta (. Vector -EMPTY) meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (hash-ordered-coll coll)) + + ISeqable + (-seq [coll] + (when (> (alength array) 0) + (let [vector-seq + (fn vector-seq [i] + (lazy-seq + (when (< i (alength array)) + (cons (aget array i) (vector-seq (inc i))))))] + (vector-seq 0)))) + + ICounted + (-count [coll] (alength array)) + + IIndexed + (-nth [coll n] + (if (and (<= 0 n) (< n (alength array))) + (aget array (int n)) + (throw (js/Error. (str "No item " n " in vector of length " (alength array)))))) + (-nth [coll n not-found] + (if (and (<= 0 n) (< n (alength array))) + (aget array (int n)) + not-found)) + + ILookup + (-lookup [coll k] + (when (number? k) + (-nth coll k nil))) + (-lookup [coll k not-found] + (if (number? k) + (-nth coll k not-found) + not-found)) + + IAssociative + (-assoc [coll k v] + (if (number? k) + (let [new-array (aclone array)] + (aset new-array k v) + (Vector. meta new-array nil)) + (throw (js/Error. "Vector's key for assoc must be a number.")))) + (-contains-key? [coll k] + (if (integer? k) + (and (<= 0 k) (< k (alength array))) + false)) + + IVector + (-assoc-n [coll n val] (-assoc coll n val)) + + IReversible + (-rseq [coll] + (let [cnt (alength array)] + (when (pos? cnt) + (RSeq. coll (dec cnt) nil)))) + + IReduce + (-reduce [v f] + (array-reduce array f)) + (-reduce [v f start] + (array-reduce array f start)) + + IKVReduce + (-kv-reduce [v f init] + (let [len (alength array)] + (loop [i 0 init init] + (if (< i len) + (let [init (f init i (aget array i))] + (if (reduced? init) + @init + (recur (inc i) init))) + init)))) + + IDrop + (-drop [v n] + (let [cnt (alength array)] + (if (< n cnt) + (prim-seq array n) + nil))) + + IComparable + (-compare [x y] + (if (vector? y) + (compare-indexed x y) + (throw (js/Error. "Cannot compare with Vector")))) + + IFn + (-invoke [coll k] + (if (number? k) + (-nth coll k) + (throw (js/Error. "Key must be integer")))) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientAssociative + (-assoc! [tcoll key val] + (-assoc-n! tcoll key val)) + + ITransientVector + (-assoc-n! [tcoll key val] + (if (number? key) + (-assoc-n tcoll key val) + (throw (js/Error. "Vector's key for assoc! must be a number.")))) + + (-pop! [tcoll] + (-pop tcoll)) + + IIterable + (-iterator [coll] + (VectorIterator. array 0)) + + IPrintWithWriter + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll))) + +(es6-iterable PersistentVector) + +(set! (. Vector -EMPTY) (Vector. nil (array) nil)) + +(set! (. Vector -fromArray) (fn [xs] (Vector. nil xs nil))) + +(defn simple-vector + [& args] + (if (and (instance? IndexedSeq args) (zero? (.-i args))) + (.fromArray Vector (aclone (.-arr args))) + (Vector. nil (into-array args) nil))) + +(defn simple-vec + [coll] + (cond + (map-entry? coll) + [(key coll) (val coll)] + + (vector? coll) + (with-meta coll nil) + + (array? coll) + (.fromArray Vector coll) + + :else + (into [] coll))) + +; The keys field is an array of all keys of this map, in no particular +; order. Any string, keyword, or symbol key is used as a property name +; to store the value in strobj. If a key is assoc'ed when that same +; key already exists in strobj, the old value is overwritten. If a +; non-string key is assoc'ed, return a HashMap object instead. + +(defn- obj-map-compare-keys [a b] + (let [a (hash a) + b (hash b)] + (cond + (< a b) -1 + (> a b) 1 + :else 0))) + +(defn- obj-clone [obj ks] + (let [new-obj (js-obj) + l (alength ks)] + (loop [i 0] + (when (< i l) + (let [k (aget ks i)] + (gobject/set new-obj k (gobject/get obj k)) + (recur (inc i))))) + new-obj)) + +(declare simple-hash-map HashMap) + +(defn- keyword->obj-map-key + [k] + (str "\uFDD0" "'" (. k -fqn))) + +(defn- obj-map-key->keyword + [k] + (if (.startsWith k "\uFDD0") + (keyword (.substring k 2 (. k -length))) + k)) + +(defn- scan-array [incr k array] + (let [len (alength array)] + (loop [i 0] + (when (< i len) + (if (identical? k (aget array i)) + i + (recur (+ i incr))))))) + +(deftype ObjMapIterator [strkeys strobj ^:mutable i] + Object + (hasNext [_] + (< i (alength strkeys))) + (next [_] + (let [k (aget strkeys i)] + (set! i (inc i)) + (MapEntry. (obj-map-key->keyword k) (unchecked-get strobj k) nil)))) + +(deftype ObjMap [meta strkeys strobj ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (keys [coll] + (es6-iterator + (prim-seq + (.map (.sort strkeys obj-map-compare-keys) + obj-map-key->keyword)))) + (entries [coll] + (es6-entries-iterator (-seq coll))) + (values [coll] + (es6-iterator + (prim-seq + (.map (.sort strkeys obj-map-compare-keys) + #(unchecked-get strobj %))))) + (has [coll k] + (contains? coll k)) + (get [coll k not-found] + (-lookup coll k not-found)) + (forEach [coll f] + (.forEach (.sort strkeys obj-map-compare-keys) + #(f (unchecked-get strobj %) (obj-map-key->keyword %)))) + + IWithMeta + (-with-meta [coll meta] (ObjMap. meta strkeys strobj __hash)) + + IMeta + (-meta [coll] meta) + + ICloneable + (-clone [coll] (ObjMap. meta strkeys strobj __hash)) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj coll entry))) + + IEmptyableCollection + (-empty [coll] (-with-meta (. ObjMap -EMPTY) meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (when (pos? (alength strkeys)) + (prim-seq + (.map (.sort strkeys obj-map-compare-keys) + #(MapEntry. (obj-map-key->keyword %) (unchecked-get strobj %) nil))))) + + ICounted + (-count [coll] (alength strkeys)) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k strkeys)))) + (unchecked-get strobj k) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (string? k) + (if-not (nil? (scan-array 1 k strkeys)) + (let [new-strobj (obj-clone strobj strkeys)] + (gobject/set new-strobj k v) + (ObjMap. meta strkeys new-strobj nil)) ;overwrite + (let [new-strobj (obj-clone strobj strkeys) ; append + new-keys (aclone strkeys)] + (gobject/set new-strobj k v) + (.push new-keys k) + (ObjMap. meta new-keys new-strobj nil))) + ; non-string key. game over. + (-with-meta + (-kv-reduce coll + (fn [ret k v] + (-assoc ret k v)) + (simple-hash-map k v)) + meta)))) + (-contains-key? [coll k] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k strkeys)))) + true + false))) + + IFind + (-find [coll k] + (let [k' (if-not (keyword? k) k (keyword->obj-map-key k))] + (when (and (string? k') + (not (nil? (scan-array 1 k' strkeys)))) + (MapEntry. k (unchecked-get strobj k') nil)))) + + IKVReduce + (-kv-reduce [coll f init] + (let [len (alength strkeys)] + (loop [keys (.sort strkeys obj-map-compare-keys) + init init] + (if (seq keys) + (let [k (first keys) + init (f init (obj-map-key->keyword k) (unchecked-get strobj k))] + (if (reduced? init) + @init + (recur (rest keys) init))) + init)))) + + IIterable + (-iterator [coll] + (ObjMapIterator. strkeys strobj 0)) + + IReduce + (-reduce [coll f] + (iter-reduce coll f)) + (-reduce [coll f start] + (iter-reduce coll f start)) + + IMap + (-dissoc [coll k] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k strkeys)))) + (let [new-keys (aclone strkeys) + new-strobj (obj-clone strobj strkeys)] + (.splice new-keys (scan-array 1 k new-keys) 1) + (js-delete new-strobj k) + (ObjMap. meta new-keys new-strobj nil)) + coll))) ; key not found, return coll unchanged + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientAssociative + (-assoc! [coll key val] + (-assoc coll key val)) + + ITransientMap + (-dissoc! [coll key] + (-dissoc coll key)) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts))) + +(es6-iterable ObjMap) + +(set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj) empty-unordered-hash)) + +(set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj nil))) + +(defn obj-map + "keyval => key val + Returns a new object map with supplied mappings." + [& keyvals] + (let [ks (array) + obj (js-obj)] + (loop [kvs (seq keyvals)] + (if kvs + (let [k (-> kvs first keyword->obj-map-key)] + (.push ks k) + (gobject/set obj k (second kvs)) + (recur (nnext kvs))) + (.fromObject ObjMap ks obj))))) + +(defn- scan-array-equiv [incr k array] + (let [len (alength array)] + (loop [i 0] + (when (< i len) + (if (= k (aget array i)) + i + (recur (+ i incr))))))) + +; The keys field is an array of all keys of this map, in no particular +; order. Each key is hashed and the result used as a property name of +; hashobj. Each values in hashobj is actually a bucket in order to handle hash +; collisions. A bucket is an array of alternating keys (not their hashes) and +; vals. +(deftype HashMap [meta count hashobj ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (keys [coll] + (es6-iterator (map #(-key %) (-seq coll)))) + (entries [coll] + (es6-entries-iterator (-seq coll))) + (values [coll] + (es6-iterator (map #(-val %) (-key coll)))) + (has [coll k] + (contains? coll k)) + (get [coll k not-found] + (-lookup coll k not-found)) + (forEach [coll f] + (let [xs (-seq coll)] + (when-not (nil? xs) + (.forEach (.-arr xs) + #(f (-val %) (-key %)))))) + + IWithMeta + (-with-meta [coll meta] (HashMap. meta count hashobj __hash)) + + IMeta + (-meta [coll] meta) + + ICloneable + (-clone [coll] (HashMap. meta count hashobj __hash)) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj coll entry))) + + IEmptyableCollection + (-empty [coll] (with-meta (. HashMap -EMPTY) meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (when (pos? count) + (let [hashes (.sort (js-keys hashobj)) + cnt (alength hashes) + arr (array)] + (loop [i 0] + (if (< i cnt) + (let [bckt (unchecked-get hashobj (aget hashes i)) + len (alength bckt)] + (loop [j 0] + (when (< j len) + (do + (.push arr (MapEntry. (aget bckt j) (aget bckt (inc j)) nil)) + (recur (+ j 2))))) + (recur (inc i))) + (prim-seq arr)))))) + + ICounted + (-count [coll] count) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (let [bucket (unchecked-get hashobj (hash k)) + i (when bucket (scan-array-equiv 2 k bucket))] + (if (some? i) + (aget bucket (inc i)) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [h (hash k) + bucket (unchecked-get hashobj h)] + (if (some? bucket) + (let [new-bucket (aclone bucket) + new-hashobj (gobject/clone hashobj) + i (scan-array-equiv 2 k new-bucket)] + (aset new-hashobj h new-bucket) + (if (some? i) + (do + ; found key, replace + (aset new-bucket (inc i) v) + (HashMap. meta count new-hashobj nil)) + (do + ; did not find key, append + (.push new-bucket k v) + (HashMap. meta (inc count) new-hashobj nil)))) + (let [new-hashobj (gobject/clone hashobj)] + ; did not find bucket + (unchecked-set new-hashobj h (array k v)) + (HashMap. meta (inc count) new-hashobj nil))))) + (-contains-key? [coll k] + (let [bucket (unchecked-get hashobj (hash k)) + i (when bucket (scan-array-equiv 2 k bucket))] + (if (some? i) + true + false))) + + IMap + (-dissoc [coll k] + (let [h (hash k) + bucket (unchecked-get hashobj h) + i (when bucket (scan-array-equiv 2 k bucket))] + (if (some? i) + (let [new-hashobj (gobject/clone hashobj)] + (if (> 3 (alength bucket)) + (js-delete new-hashobj h) + (let [new-bucket (aclone bucket)] + (.splice new-bucket i 2) + (unchecked-set new-hashobj h new-bucket))) + (HashMap. meta (dec count) new-hashobj nil)) + ; key not found, return coll unchanged + coll))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientAssociative + (-assoc! [coll key val] + (-assoc coll key val)) + + ITransientMap + (-dissoc! [coll key] + (-dissoc coll key)) + + IIterable + (-iterator [coll] + (let [xs (-seq coll)] + (if (some? xs) + (-iterator xs) + (nil-iter)))) + + IKVReduce + (-kv-reduce [coll f init] + (let [hashes (.sort (js-keys hashobj)) + ilen (alength hashes)] + (loop [i 0 init init] + (if (< i ilen) + (let [bckt (unchecked-get hashobj (aget hashes i)) + jlen (alength bckt) + init (loop [j 0 init init] + (if (< j jlen) + (let [init (f init (aget bckt j) (aget bckt (inc j)))] + (if (reduced? init) + init + (recur (+ j 2) init))) + init))] + (if (reduced? init) + @init + (recur (inc i) init))) + init)))) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts))) + +(es6-iterable HashMap) + +(set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj) empty-unordered-hash)) + +(set! (. HashMap -fromArrays) (fn [ks vs] + (let [len (.-length ks)] + (loop [i 0, out (. HashMap -EMPTY)] + (if (< i len) + (recur (inc i) (assoc out (aget ks i) (aget vs i))) + out))))) + +(defn simple-hash-map + "keyval => key val + Returns a new hash map with supplied mappings." + [& keyvals] + (loop [in (seq keyvals), out (. HashMap -EMPTY)] + (if in + (recur (nnext in) (-assoc out (first in) (second in))) + out))) + +(deftype Set [meta hash-map ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (keys [coll] + (es6-iterator (-seq coll))) + (entries [coll] + (es6-set-entries-iterator (-seq coll))) + (values [coll] + (es6-iterator (-seq coll))) + (has [coll k] + (contains? coll k)) + (forEach [coll f] + (let [xs (-seq hash-map)] + (when (some? xs) + (.forEach (.-arr xs) + #(f (-val %) (-key %)))))) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (Set. new-meta hash-map __hash))) + + IMeta + (-meta [coll] meta) + + ICloneable + (-clone [coll] (Set. meta hash-map __hash)) + + ICollection + (-conj [coll o] + (Set. meta (assoc hash-map o o) nil)) + + IEmptyableCollection + (-empty [coll] (with-meta (. Set -EMPTY) meta)) + + IEquiv + (-equiv [coll other] + (and + (set? other) + (= (-count coll) (count other)) + (every? #(contains? coll %) + other))) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (let [xs (-seq hash-map)] + (when (some? xs) + (prim-seq (.map (.-arr xs) (fn [kv] (-key kv))))))) + + ICounted + (-count [coll] + (let [xs (-seq coll)] + (if (some? xs) + (-count xs) + 0))) + + ILookup + (-lookup [coll v] + (-lookup coll v nil)) + (-lookup [coll v not-found] + (if (-contains-key? hash-map v) + (-lookup hash-map v) + not-found)) + + ISet + (-disjoin [coll v] + (Set. meta (-dissoc hash-map v) nil)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientSet + (-disjoin! [coll key] + (-disjoin coll key)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IIterable + (-iterator [coll] + (let [xs (-seq coll)] + (if (some? xs) + (-iterator xs) + (nil-iter)))) + + IPrintWithWriter + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll))) + +(es6-iterable Set) + +(set! (. Set -EMPTY) (Set. nil (. HashMap -EMPTY) empty-unordered-hash)) + +(defn simple-set + [coll] + (if (set? coll) + (-with-meta coll nil) + (let [in (seq coll)] + (if (nil? in) + #{} + (loop [in in out (. Set -EMPTY)] + (if-not (nil? in) + (recur (next in) (-conj out (first in))) + out)))))) diff --git a/src/main/cljs/cljs/spec/alpha.cljs b/src/main/cljs/cljs/spec/alpha.cljs index c88079f5f..b348d9928 100644 --- a/src/main/cljs/cljs/spec/alpha.cljs +++ b/src/main/cljs/cljs/spec/alpha.cljs @@ -148,6 +148,10 @@ (specize* ([s] (spec-impl s s nil nil)) ([s form] (spec-impl form s nil nil))) + Set + (specize* ([s] (spec-impl s s nil nil)) + ([s form] (spec-impl form s nil nil))) + default (specize* ([o] diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 709531e59..ceebbe973 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -16,6 +16,7 @@ #?(:clj (:require [cljs.analyzer.impl :as impl] [cljs.analyzer.impl.namespaces :as nses] [cljs.analyzer.passes.and-or :as and-or] + [cljs.analyzer.passes.lite :as lite] [cljs.env :as env :refer [ensure]] [cljs.externs :as externs] [cljs.js-deps :as deps] @@ -30,6 +31,7 @@ :cljs (:require [cljs.analyzer.impl :as impl] [cljs.analyzer.impl.namespaces :as nses] [cljs.analyzer.passes.and-or :as and-or] + [cljs.analyzer.passes.lite :as lite] [cljs.env :as env] [cljs.reader :as edn] [cljs.tagged-literals :as tags] @@ -492,6 +494,12 @@ (def ^:dynamic *cljs-warning-handlers* [default-warning-handler]) +(defn lite-mode? [] + (get-in @env/*compiler* [:options :lite-mode])) + +(defn elide-to-string? [] + (get-in @env/*compiler* [:options :elide-to-string])) + #?(:clj (defmacro with-warning-handlers [handlers & body] `(binding [*cljs-warning-handlers* ~handlers] @@ -4072,8 +4080,10 @@ (if (and (some? nsym) (symbol? nsym)) (.findInternedVar ^clojure.lang.Namespace #?(:clj (find-ns nsym) :cljs (find-macros-ns nsym)) sym) - (.findInternedVar ^clojure.lang.Namespace - #?(:clj (find-ns 'cljs.core) :cljs (find-macros-ns impl/CLJS_CORE_MACROS_SYM)) sym))))))) + ;; can't be done as compiler pass because macros get to run first + (when-not (and (lite-mode?) (= 'vector sym)) + (.findInternedVar ^clojure.lang.Namespace + #?(:clj (find-ns 'cljs.core) :cljs (find-macros-ns impl/CLJS_CORE_MACROS_SYM)) sym)))))))) (defn get-expander "Given a sym, a symbol identifying a macro, and env, an analysis environment @@ -4452,10 +4462,8 @@ :cljs [infer-type and-or/optimize check-invoke-arg-types])) (defn analyze* [env form name opts] - (let [passes *passes* - passes (if (nil? passes) - default-passes - passes) + (let [passes (cond-> (or *passes* default-passes) + (lite-mode?) (conj lite/use-lite-types)) form (if (instance? LazySeq form) (if (seq form) form ()) form) diff --git a/src/main/clojure/cljs/closure.clj b/src/main/clojure/cljs/closure.clj index b215573f6..550fd68d5 100644 --- a/src/main/clojure/cljs/closure.clj +++ b/src/main/clojure/cljs/closure.clj @@ -211,7 +211,8 @@ :watch :watch-error-fn :watch-fn :install-deps :process-shim :rename-prefix :rename-prefix-namespace :closure-variable-map-in :closure-property-map-in :closure-variable-map-out :closure-property-map-out :stable-names :ignore-js-module-exts :opts-cache :aot-cache :elide-strict :fingerprint :spec-skip-macros - :nodejs-rt :target-fn :deps-cmd :bundle-cmd :global-goog-object&array :node-modules-dirs}) + :nodejs-rt :target-fn :deps-cmd :bundle-cmd :global-goog-object&array :node-modules-dirs :lite-mode + :elide-to-string}) (def string->charset {"iso-8859-1" StandardCharsets/ISO_8859_1 @@ -2519,6 +2520,10 @@ :cache-analysis-format (:cache-analysis-format opts :transit)) (update-in [:preamble] #(into (or % []) ["cljs/imul.js"]))) + (:lite-mode opts) + (assoc-in [:closure-defines (str (comp/munge 'cljs.core/LITE_MODE))] + (:lite-mode opts)) + (:target opts) (assoc-in [:closure-defines (str (comp/munge 'cljs.core/*target*))] (name (:target opts))) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index fcc03ab96..faba462b5 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -522,6 +522,27 @@ (and (every? #(= (:op %) :const) keys) (= (count (into #{} keys)) (count keys))))) +(defn obj-map-key [x] + (if (keyword? x) + (str \" "\\uFDD0" \' + (if (namespace x) + (str (namespace x) "/") "") + (name x) + \") + (str \" x \"))) + +(defn emit-obj-map [str-keys vals comma-sep distinct-keys?] + (if (zero? (count str-keys)) + (emits "cljs.core.ObjMap.EMPTY") + (emits "cljs.core.ObjMap.fromObject([" (comma-sep str-keys) "], {" + (comma-sep (map (fn [k v] (str k ":" (emit-str v))) str-keys vals)) + "})"))) + +(defn emit-lite-map [keys vals comma-sep distinct-keys?] + (if (zero? (count keys)) + (emits "cljs.core.HashMap.EMPTY") + (emits "cljs.core.HashMap.fromArrays([" (comma-sep keys) "], [" (comma-sep vals) "])"))) + (defn emit-map [keys vals comma-sep distinct-keys?] (cond (zero? (count keys)) @@ -544,9 +565,14 @@ "])"))) (defmethod emit* :map - [{:keys [env keys vals]}] + [{:keys [env form keys vals]}] (emit-wrap env - (emit-map keys vals comma-sep distinct-keys?))) + (if (ana/lite-mode?) + (let [form-keys (clojure.core/keys form)] + (if (every? #(or (string? %) (keyword? %)) form-keys) + (emit-obj-map (map obj-map-key form-keys) vals comma-sep distinct-keys?) + (emit-lite-map keys vals comma-sep distinct-keys?))) + (emit-map keys vals comma-sep distinct-keys?)))) (defn emit-list [items comma-sep] (if (empty? items) @@ -562,10 +588,17 @@ ", 5, cljs.core.PersistentVector.EMPTY_NODE, [" (comma-sep items) "], null)") (emits "cljs.core.PersistentVector.fromArray([" (comma-sep items) "], true)"))))) +(defn emit-lite-vector [items comma-sep] + (if (empty? items) + (emits "cljs.core.Vector.EMPTY") + (emits "new cljs.core.Vector(null, [" (comma-sep items) "], null)"))) + (defmethod emit* :vector [{:keys [items env]}] (emit-wrap env - (emit-vector items comma-sep))) + (if (ana/lite-mode?) + (emit-lite-vector items comma-sep) + (emit-vector items comma-sep)))) (defn distinct-constants? [items] (let [items (map ana/unwrap-quote items)] @@ -583,10 +616,17 @@ :else (emits "cljs.core.PersistentHashSet.createAsIfByAssoc([" (comma-sep items) "])"))) +(defn emit-lite-set [items comma-sep distinct-constants?] + (if (empty? items) + (emits "cljs.core.Set.EMPTY") + (emits "cljs.core.simple_set([" (comma-sep items) "])"))) + (defmethod emit* :set [{:keys [items env]}] (emit-wrap env - (emit-set items comma-sep distinct-constants?))) + (if (ana/lite-mode?) + (emit-lite-set items comma-sep distinct-constants?) + (emit-set items comma-sep distinct-constants?)))) (defn emit-js-object [items emit-js-object-val] (emits "({") diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 8393a1a67..b326a3fa6 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -1507,13 +1507,18 @@ ~@body)))) (core/defn- add-obj-methods [type type-sym sigs] - (map (core/fn [[f & meths :as form]] - (core/let [[f meths] (if (vector? (first meths)) - [f [(rest form)]] - [f meths])] - `(set! ~(extend-prefix type-sym f) - ~(with-meta `(fn ~@(map #(adapt-obj-params type %) meths)) (meta form))))) - sigs)) + (core/->> sigs + ;; Elide all toString methods in :lite-mode + (remove + (core/fn [[f]] + (core/and (ana/elide-to-string?) (core/= 'toString f)))) + (map + (core/fn [[f & meths :as form]] + (core/let [[f meths] (if (vector? (first meths)) + [f [(rest form)]] + [f meths])] + `(set! ~(extend-prefix type-sym f) + ~(with-meta `(fn ~@(map #(adapt-obj-params type %) meths)) (meta form)))))))) (core/defn- ifn-invoke-methods [type type-sym [f & meths :as form]] (map diff --git a/src/test/cljs/cljs/collections_test.cljs b/src/test/cljs/cljs/collections_test.cljs index 44d5e3f46..20aae48f0 100644 --- a/src/test/cljs/cljs/collections_test.cljs +++ b/src/test/cljs/cljs/collections_test.cljs @@ -9,12 +9,9 @@ (ns cljs.collections-test (:refer-clojure :exclude [iter]) (:require [cljs.test :refer-macros [deftest testing is are run-tests]] - [clojure.test.check :as tc] [clojure.test.check.clojure-test :refer-macros [defspec]] [clojure.test.check.generators :as gen] - [clojure.test.check.properties :as prop :include-macros true] - [clojure.string :as s] - [clojure.set :as set])) + [clojure.test.check.properties :as prop :include-macros true])) (deftest test-map-operations (testing "Test basic map collection operations" @@ -196,8 +193,10 @@ (is (not (counted? (range)))) (is (counted? (range 0 10 1))) (is (not (counted? (range 0.1 10 1)))) - (is (chunked-seq? (range 0 10 1))) - (is (chunked-seq? (range 0.1 10 1))) + ;; no chunked seqs in :lite-mode + (when-not ^boolean LITE_MODE + (is (chunked-seq? (range 0 10 1))) + (is (chunked-seq? (range 0.1 10 1)))) (is (= (range 0.5 8 1.2) '(0.5 1.7 2.9 4.1 5.3 6.5 7.7))) (is (= (range 0.5 -4 -2) '(0.5 -1.5 -3.5))) (testing "IDrop" @@ -1020,8 +1019,9 @@ (deftest test-cljs-2128 (testing "Subvec iteration" - (testing "Subvec over PersistentVector uses RangedIterator" - (is (instance? RangedIterator (-iterator (subvec [0 1 2 3] 1 3))))) + (when-not ^boolean LITE_MODE + (testing "Subvec over PersistentVector uses RangedIterator" + (is (instance? RangedIterator (-iterator (subvec [0 1 2 3] 1 3)))))) (testing "Subvec over other vectors uses naive SeqIter" (is (instance? SeqIter (-iterator (subvec (->CustomVectorThing [0 1 2 3]) 1 3)))))) (testing "Subvec reduce" @@ -1127,9 +1127,12 @@ (next (chunk-cons (chunk b) nil)))))) (deftest test-cljs-3124 - (let [t (assoc! (transient []) 0 1)] - (persistent! t) - (is (= :fail (try (get t :a :not-found) (catch js/Error e :fail)))))) + ;; Doesn't work under :lite-mode because there are not + ;; separate transient types for now + (when-not ^boolean LITE_MODE + (let [t (assoc! (transient []) 0 1)] + (persistent! t) + (is (= :fail (try (get t :a :not-found) (catch js/Error e :fail))))))) (deftest test-cljs-3317 (testing "persistent vector invoke matches clojure" @@ -1157,6 +1160,62 @@ (let [things (zipmap (range 15000) (repeat 0))] (is (zero? (count (filter #(-> % key string?) things)))))) +(deftest test-obj-map + (let [a (obj-map)] + (is (empty? a)) + (is (zero? (count a)))) + (let [b (obj-map :a 1)] + (is (not (empty? b))) + (is (== 1 (count b)))) + (let [c (obj-map :a 1 :b 2 :c 3)] + (is (== 3 (count c))) + (is (= 1 (get c :a))) + (is (= 1 (:a c))) + (is (every? keyword? (keys c))) + (is (= (set [:a :b :c]) (set (keys c))))) + (is (= (obj-map :a 1 :b 2 :c 3) + (obj-map :a 1 :b 2 :c 3))) + (is (= (obj-map :a 1 :b 2) + (into (obj-map) [[:a 1] [:b 2]]))) + (is (= (merge-with + + (obj-map :a 1 :b 2) + (obj-map :a 1 :b 2)) + (into (obj-map) [[:a 2] [:b 4]]))) + (is (= (transient (obj-map :a 1 :b 2)) + (obj-map :a 1 :b 2)))) + +(deftest test-simple-hash-map + (let [a (simple-hash-map)] + (is (empty? a)) + (is (zero? (count a)))) + (let [b (simple-hash-map :a 1)] + (is (not (empty? b))) + (is (== 1 (count b)))) + (let [c (simple-hash-map :a 1 :b 2 :c 3)] + (is (== 3 (count c))) + (is (= 1 (get c :a))) + (is (= 1 (:a c))) + (is (every? keyword? (keys c))) + (is (= (set [:a :b :c]) (set (keys c))))) + (is (= (simple-hash-map :a 1 :b 2 :c 3) + (simple-hash-map :a 1 :b 2 :c 3))) + (is (= (simple-hash-map :a 1 :b 2) + (into (simple-hash-map) [[:a 1] [:b 2]]))) + (is (= (merge-with + + (simple-hash-map :a 1 :b 2) + (simple-hash-map :a 1 :b 2)) + (into (simple-hash-map) [[:a 2] [:b 4]]))) + (is (= (transient (simple-hash-map :a 1 :b 2)) + (simple-hash-map :a 1 :b 2)))) + +(deftest test-simple-set + (is (= #{1 2 3} #{1 2 3})) + (is (= 3 (count #{1 2 3}))) + (let [x #{1 2 3}] + (is (every? #(contains? x %) [1 2 3]))) + (is (= (simple-set [[3 4] [1 2] [5 6]]) + (into #{} [[3 4] [1 2] [5 6]])))) + (comment (run-tests) diff --git a/src/test/cljs/cljs/interop_test.cljs b/src/test/cljs/cljs/interop_test.cljs index ee158ec18..f736ea15c 100644 --- a/src/test/cljs/cljs/interop_test.cljs +++ b/src/test/cljs/cljs/interop_test.cljs @@ -14,12 +14,17 @@ (testing "Object equiv method" (is (.equiv :foo :foo)) (is (.equiv 'foo 'foo)) - (is (.equiv {:foo 1 :bar 2} {:foo 1 :bar 2})) + ;; .equiv is not a standard thing, primarily for interop + ;; in transit-js, probably not a concern for lite-mode users + (when-not LITE_MODE + (is (.equiv {:foo 1 :bar 2} {:foo 1 :bar 2}))) (is (.equiv [1 2 3] [1 2 3])) (is (.equiv '(1 2 3) '(1 2 3))) (is (.equiv (map inc [1 2 3]) (map inc [1 2 3]))) - (is (.equiv #{:cat :dog :bird} #{:cat :dog :bird})) - )) + ;; .equiv is not a standard thing, primarily for interop + ;; in transit-js, probably not a concern for lite-mode users + (when-not LITE_MODE + (is (.equiv #{:cat :dog :bird} #{:cat :dog :bird}))))) (deftest test-es6-interfaces (testing "ES6 collection interfaces" @@ -67,4 +72,4 @@ (is (#{:cat :bird :dog} (.-value (.next iter)))) (is (#{:cat :bird :dog} (.-value (.next iter)))) (is (.-done (.next iter))))) - )) \ No newline at end of file + )) diff --git a/src/test/cljs/cljs/lite_collections_test.cljs b/src/test/cljs/cljs/lite_collections_test.cljs new file mode 100644 index 000000000..f481be9ff --- /dev/null +++ b/src/test/cljs/cljs/lite_collections_test.cljs @@ -0,0 +1,32 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.lite-collections-test + (:require [cljs.test :refer [deftest testing is]])) + +;; NOTE: ** this namespace must be tested with :lite-mode true ** + +(deftest test-obj-map + (let [a (. ObjMap -EMPTY) + b {}] + (is (identical? a b))) + (let [a {:foo 1}] + (is (== 1 (:foo a))))) + +(deftest test-simple-set-with-set + (is (= (simple-set []) (set []))) + (is (= (set []) (simple-set []))) + (is (= (simple-set [(MapEntry. 1 2 nil)]) + (set [(MapEntry. 1 2 nil)])))) + +(comment + + (require '[cljs.lite-collections-test] :reload) + (cljs.test/run-tests) + + ) diff --git a/src/test/cljs/cljs/metadata_test.cljc b/src/test/cljs/cljs/metadata_test.cljc index b22c79612..8f5e99411 100644 --- a/src/test/cljs/cljs/metadata_test.cljc +++ b/src/test/cljs/cljs/metadata_test.cljc @@ -109,11 +109,15 @@ (seq-interface-tests (seq [])) (seq-interface-tests (rseq []))) (testing "Medium" - (seq-interface-tests (seq [0 1 2 3])) - (seq-interface-tests (rseq [0 1 2 3]))) + (testing "seq" + (seq-interface-tests (seq [0 1 2 3]))) + (testing "rseq" + (seq-interface-tests (rseq [0 1 2 3])))) (testing "Large" - (seq-interface-tests (seq (vec (range 100)))) - (seq-interface-tests (rseq (vec (range 100)))))) + (testing "seq" + (seq-interface-tests (seq (vec (range 100))))) + (testing "rseq" + (seq-interface-tests (rseq (vec (range 100))))))) (testing "PersistentHashSet" (testing "Empty" diff --git a/src/test/cljs/cljs/seqs_test.cljs b/src/test/cljs/cljs/seqs_test.cljs index 9e43a7340..00d648fa1 100644 --- a/src/test/cljs/cljs/seqs_test.cljs +++ b/src/test/cljs/cljs/seqs_test.cljs @@ -40,7 +40,11 @@ (testing "lazy seq" (is (seq? e-lazy-seq)) (is (empty? e-lazy-seq)) - (is (= {:b :c} (meta e-lazy-seq))))) + ;; MAYBE FIXME: this is a bad test, discovered from :lite-mode work + (if-not ^boolean LITE_MODE + (is (= {:b :c} (meta e-lazy-seq))) + ;; LITE_MODE has the correct behavior + (is (nil? (meta e-lazy-seq)))))) (let [e-list (empty '^{:b :c} (1 2 3))] (testing "list" (is (seq? e-list)) @@ -203,20 +207,21 @@ (is (= (.lastIndexOf (sequence (map inc) '(0 1 0 3 4)) 1) 2)))) (deftest test-chunked - (let [r (range 64) - v (into [] r)] - (testing "Testing Chunked Seqs" - (is (= (hash (seq v)) (hash (seq v)))) - (is (= 6 (reduce + (array-chunk (array 1 2 3))))) - (is (instance? ChunkedSeq (seq v))) - (is (= r (seq v))) - (is (= (map inc r) (map inc v))) - (is (= (filter even? r) (filter even? v))) - (is (= (filter odd? r) (filter odd? v))) - (is (= (concat r r r) (concat v v v))) - (is (satisfies? IReduce (seq v))) - (is (== 2010 (reduce + (nnext (nnext (seq v)))))) - (is (== 2020 (reduce + 10 (nnext (nnext (seq v))))))))) + (when-not LITE_MODE + (let [r (range 64) + v (into [] r)] + (testing "Testing Chunked Seqs" + (is (= (hash (seq v)) (hash (seq v)))) + (is (= 6 (reduce + (array-chunk (array 1 2 3))))) + (is (instance? ChunkedSeq (seq v))) + (is (= r (seq v))) + (is (= (map inc r) (map inc v))) + (is (= (filter even? r) (filter even? v))) + (is (= (filter odd? r) (filter odd? v))) + (is (= (concat r r r) (concat v v v))) + (is (satisfies? IReduce (seq v))) + (is (== 2010 (reduce + (nnext (nnext (seq v)))))) + (is (== 2020 (reduce + 10 (nnext (nnext (seq v)))))))))) (deftest test-778 (testing "Testing CLJS-778, -rest, -next RSeq" diff --git a/src/test/cljs/cljs/walk_test.cljs b/src/test/cljs/cljs/walk_test.cljs index 9f28ebf08..fb3476f53 100644 --- a/src/test/cljs/cljs/walk_test.cljs +++ b/src/test/cljs/cljs/walk_test.cljs @@ -51,28 +51,29 @@ (defmethod get-comparator PersistentTreeSet [o] (get-comparator (.-tree-map o))) -(deftest walk - "Checks that walk returns the correct result and type of collection" - (let [colls ['(1 2 3) - [1 2 3] - #{1 2 3} - (sorted-set-by > 1 2 3) - {:a 1, :b 2, :c 3} - (sorted-map-by > 1 10, 2 20, 3 30) - (->Foo 1 2 3) - (map->Foo {:a 1 :b 2 :c 3 :extra 4})]] - (doseq [c colls] - (let [walked (w/walk identity identity c)] - (is (= c walked)) - ;;(is (= (type c) (type walked))) - (if (map? c) - (is (= (w/walk #(update-in % [1] inc) #(reduce + (vals %)) c) - (reduce + (map (comp inc val) c)))) - (is (= (w/walk inc #(reduce + %) c) - (reduce + (map inc c))))) - (when (or (instance? PersistentTreeMap c) - (instance? PersistentTreeSet c)) - (is (= (get-comparator c) (get-comparator walked)))))))) +(deftest test-walk + (testing "Test that walk returns the correct result\n" + (let [colls ['(1 2 3) + [1 2 3] + #{1 2 3} + (sorted-set-by > 1 2 3) + {:a 1, :b 2, :c 3} + (sorted-map-by > 1 10, 2 20, 3 30) + (->Foo 1 2 3) + (map->Foo {:a 1 :b 2 :c 3 :extra 4})]] + (doseq [c colls] + (testing (str "Walking ... " c) + (let [walked (w/walk identity identity c)] + (is (= c walked)) + ;;(is (= (type c) (type walked))) + (if (map? c) + (is (= (w/walk #(update-in % [1] inc) #(reduce + (vals %)) c) + (reduce + (map (comp inc val) c)))) + (is (= (w/walk inc #(reduce + %) c) + (reduce + (map inc c))))) + (when (or (instance? PersistentTreeMap c) + (instance? PersistentTreeSet c)) + (is (= (get-comparator c) (get-comparator walked)))))))))) (deftest walk-mapentry "Checks that walk preserves the MapEntry type. See CLJS-2909." diff --git a/src/test/cljs/lite_test_runner.cljs b/src/test/cljs/lite_test_runner.cljs new file mode 100644 index 000000000..95313b3f8 --- /dev/null +++ b/src/test/cljs/lite_test_runner.cljs @@ -0,0 +1,127 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns lite-test-runner + (:require [cljs.test :refer-macros [run-tests]] + [cljs.apply-test] + [cljs.primitives-test] + [cljs.destructuring-test] + [cljs.new-new-test] + [cljs.printing-test] + [cljs.seqs-test] + [cljs.collections-test] + [cljs.hashing-test] + [cljs.core-test] + ;; [cljs.chunked-seq] ;; doesn't exist in :lite-mode + [cljs.interop-test] + [cljs.iterator-test] + [cljs.reader-test] + [cljs.binding-test] + [cljs.parse-test] + [cljs.ns-test] + [clojure.set-test] + [clojure.string-test] + [clojure.data-test] + [clojure.datafy-test] + [clojure.edn-test] + [clojure.walk-test] + [clojure.math-test] + [cljs.macro-test] + [cljs.letfn-test] + [foo.ns-shadow-test] + [cljs.top-level] + [cljs.reducers-test] + [cljs.keyword-test] + [cljs.import-test] + [cljs.ns-test.foo] + [cljs.syntax-quote-test] + [cljs.pprint] + [cljs.pprint-test] + [cljs.spec-test] + [cljs.specials-test] + [cljs.spec.test-test] + [cljs.clojure-alias-test] + ;; [cljs.hash-map-test] + ;; [cljs.map-entry-test] + [cljs.metadata-test] + [cljs.npm-deps-test] + [cljs.other-functions-test] + [cljs.predicates-test] + [cljs.tagged-literals-test] + [cljs.test-test] + [static.core-test] + [cljs.recur-test] + [cljs.array-access-test] + [cljs.inference-test] + [cljs.walk-test] + [cljs.repl-test] + [cljs.lite-collections-test] + [cljs.extend-to-native-test] + [cljs.var-test])) + +(set! *print-newline* false) + +;; When testing Windows we default to Node.js +(if (exists? js/print) + (set-print-fn! js/print) + (enable-console-print!)) + +(run-tests + 'cljs.apply-test + 'cljs.primitives-test + 'cljs.destructuring-test + 'cljs.printing-test + 'cljs.new-new-test + 'cljs.seqs-test + 'cljs.collections-test + 'cljs.hashing-test + 'cljs.core-test + 'cljs.interop-test ;; ES6 stuff + 'cljs.iterator-test + 'cljs.reader-test + 'cljs.binding-test + 'cljs.parse-test + 'cljs.ns-test + 'clojure.set-test + 'clojure.string-test + 'clojure.data-test + 'clojure.datafy-test + 'clojure.edn-test + 'clojure.walk-test + 'clojure.math-test + 'cljs.macro-test + 'cljs.letfn-test + 'foo.ns-shadow-test + 'cljs.top-level + 'cljs.reducers-test + 'cljs.keyword-test + 'cljs.import-test + 'cljs.ns-test.foo + 'cljs.syntax-quote-test + 'cljs.pprint + 'cljs.pprint-test + 'cljs.spec-test + 'cljs.specials-test + 'cljs.spec.test-test + 'cljs.clojure-alias-test + 'cljs.metadata-test + 'cljs.npm-deps-test + 'cljs.other-functions-test + 'cljs.predicates-test + 'cljs.tagged-literals-test + 'cljs.test-test + 'static.core-test + 'cljs.recur-test + 'cljs.array-access-test + 'cljs.inference-test + 'cljs.walk-test + 'cljs.repl-test + 'cljs.lite-collections-test + 'cljs.extend-to-native-test + 'cljs.var-test + ) diff --git a/src/test/cljs_build/trivial/core6.cljs b/src/test/cljs_build/trivial/core6.cljs new file mode 100644 index 000000000..3eed31bc6 --- /dev/null +++ b/src/test/cljs_build/trivial/core6.cljs @@ -0,0 +1,3 @@ +(ns trivial.core6) + +(.log js/console (->> (map inc (range 10)) (filter even?) (partition 2) (drop 1) (mapcat identity) into-array)) diff --git a/src/test/clojure/cljs/analyzer_pass_tests.clj b/src/test/clojure/cljs/analyzer_pass_tests.clj index c87ec7bce..1a451d491 100644 --- a/src/test/clojure/cljs/analyzer_pass_tests.clj +++ b/src/test/clojure/cljs/analyzer_pass_tests.clj @@ -178,8 +178,27 @@ (map (fn [x] x) s))))]))))] (is (empty? (re-seq #"or_" code)))))) +(deftest test-lite-mode-pass + (let [aenv (assoc (ana/empty-env) :context :expr) + env (env/default-compiler-env {:lite-mode true})] + (let [ast (env/with-compiler-env env + (comp/with-core-cljs {} + (fn [] + (analyze aenv 'cljs.core/vec))))] + (is (= 'cljs.core/simple-vec + (-> ast :name) + (-> ast :info :name)))) + (let [ast (env/with-compiler-env env + (comp/with-core-cljs {} + (fn [] + (analyze aenv 'cljs.core/vector))))] + (is (= 'cljs.core/simple-vector + (-> ast :name) + (-> ast :info :name)))))) + (comment (test/run-tests) (require '[clojure.pprint :refer [pprint]]) + ) diff --git a/src/test/clojure/cljs/build_api_tests.clj b/src/test/clojure/cljs/build_api_tests.clj index a1a2f3871..535f1c2c0 100644 --- a/src/test/clojure/cljs/build_api_tests.clj +++ b/src/test/clojure/cljs/build_api_tests.clj @@ -757,7 +757,23 @@ cenv (env/default-compiler-env)] (test/delete-out-files out) (build/build (build/inputs (io/file inputs "trivial/core4.cljs")) opts cenv) - (is (< (.length out-file) 32768)))) + (is (< (.length out-file) 92160)))) + +(deftest lite-mode-vector-code-size-ratchet + (testing ":lite-mode + :elide-to-string, should cut output size for [] in 1/2" + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-vector-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core4 + :output-dir out + :output-to (.getPath out-file) + :lite-mode true + :elide-to-string true + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core4.cljs")) opts cenv) + (is (< (.length out-file) 16384))))) (deftest trivial-output-size-map (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-map-test-out")) @@ -772,6 +788,38 @@ (build/build (build/inputs (io/file inputs "trivial/core5.cljs")) opts cenv) (is (< (.length out-file) 92160)))) +(deftest lite-mode-map-code-size-ratchet + (testing ":lite-mode + :elide-to-string, should cut output size for {} in 1/3" + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-map-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core5 + :output-dir out + :output-to (.getPath out-file) + :lite-mode true + :elide-to-string true + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core5.cljs")) opts cenv) + (is (< (.length out-file) 32768))))) + +(deftest lite-mode-api-code-size-ratchet + (testing ":lite-mode + :elide-to-string, typical cljs.core api usage ~32K" + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-map-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core6 + :output-dir out + :output-to (.getPath out-file) + :lite-mode true + :elide-to-string true + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core6.cljs")) opts cenv) + (is (< (.length out-file) 34000))))) + (deftest cljs-3255-nil-inputs-build (let [out (.getPath (io/file (test/tmp-dir) "3255-test-out")) out-file (io/file out "main.js") From 00ac012a22703888b1eaff0b43be63739a114c0b Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sat, 4 Oct 2025 10:04:34 -0400 Subject: [PATCH 30/94] CLJS-3451: make munge-str public (#269) * CLJS-3451: make munge-str public --------- Co-authored-by: Michiel Borkent --- src/main/cljs/cljs/core.cljs | 12 ++++++++++-- src/main/clojure/cljs/compiler.cljc | 2 +- src/test/cljs/cljs/core_test.cljs | 8 ++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 8eabe774b..27464f4f0 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -11887,7 +11887,9 @@ reduces them without incurring seq initialization" (str_ ret "|\\$")))))) DEMUNGE_PATTERN) -(defn- ^string munge-str [name] +(defn ^string munge-str + "Munge string `name` without considering `..` or JavaScript reserved keywords." + [name] (let [sb (StringBuffer.)] (loop [i 0] (if (< i (. name -length)) @@ -11899,7 +11901,13 @@ reduces them without incurring seq initialization" (recur (inc i))))) (.toString sb))) -(defn munge [name] +(defn munge + "Munge symbol or string `name` for safe use in JavaScript. + + - Replaces '..' with '_DOT__DOT_'. + - Appends '$' to JavaScript reserved keywords. + - Returns a symbol if `name` was a symbol, otherwise a string." + [name] (let [name' (munge-str (str_ name)) name' (cond (identical? name' "..") "_DOT__DOT_" diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index faba462b5..522a88357 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -142,7 +142,7 @@ ss (map rf (string/split ss #"\.")) ss (string/join "." ss) ms #?(:clj (clojure.lang.Compiler/munge ss) - :cljs (#'cljs.core/munge-str ss))] + :cljs (munge-str ss))] (if (symbol? s) (symbol ms) ms))))) diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index 686a0fd72..d92fb5105 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -1136,7 +1136,11 @@ (is (= "_DOT__DOT_" (munge ".."))) (is (= "abstract$" (munge "abstract"))) (is (= 'abc (munge 'abc))) - (is (= "toString" (munge "toString")))) + (is (= "toString" (munge "toString"))) + (is (= "function$" (munge "function")))) + +(deftest test-munge-str + (is (= "function" (munge-str "function")))) (defprotocol IFooBar (a-method [t])) @@ -1952,4 +1956,4 @@ (is (= "12" (apply cljs.core/str_ 1 [2]))) (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])))) \ No newline at end of file + (is (= "1234" (apply cljs.core/str_ 1 2 [3 4])))) From be21ba13822c2b3edcce0d3fac6d46a5fc6e6d0d Mon Sep 17 00:00:00 2001 From: David Nolen Date: Fri, 10 Oct 2025 14:08:24 -0400 Subject: [PATCH 31/94] CLJS-3233: `:refer-global` + `:only`, `:require-global` (#263) New `ns` form cases `:refer-global` and `:require-global` Global values and values in ClojureScript must always be referenced by prefixing the magic `js` namespace. None of the usual affordances for managing names are available for these cases. `:refer-global` and `:require-global` provide these missing affordances. `:refer-global` allows users to refer to JavaScript global objects. For example: (ns foo (:refer-global :only [Date])) Now `Date` can be used in the namespace w/o needing to write `js/Date`. Note that ClojureScript will track these globals values as being "tainted", i.e. externs inference will continue to work for these cases. Renames are supported: (ns foo (:refer-global :only [Date] :rename {Date MyDate})) `:require-global` allows users to treat some global JavaScript object as a library. This streamlines many cases where users may wish to avoid additional ceremony around build tooling. (ns foo (:require-global [SomeLib :as lib :refer [bar]])) All of the usul expecations for `:require` are supported, renames, sublibs etc. This feature builds upon existing `:global-exports` foreign library functionalty. Foreign lib entries can now declare `:external?` to communicate to the ClojureScript compiler that no source file exists for the entry. `:require-global` leverages this new facility. You can now simply include script tags on the page, and from ClojureScript use that library w/o any additional configuration. Both of the above cases have corresponding `ns*` macro variants permitting interactive usage at the REPL: (refer-global :only '[Date]) (require-global '[SomeLib :as lib :refer [bar]]) --- src/main/clojure/cljs/analyzer.cljc | 143 ++++++++++++++++++++--- src/main/clojure/cljs/closure.clj | 24 ++-- src/main/clojure/cljs/compiler.cljc | 5 +- src/main/clojure/cljs/core.cljc | 16 ++- src/main/clojure/cljs/repl.cljc | 11 +- src/test/clojure/cljs/analyzer_tests.clj | 60 +++++++++- 6 files changed, 228 insertions(+), 31 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index ceebbe973..924ac3477 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -2781,6 +2781,17 @@ (if (and (.exists cljcf) (.isFile cljcf)) cljcf)))))) +(defn external-dep? + "Returns true if the library is an :external? foreign dep. This means no source is provided + for the library, i.e. it will be provided by some script tag on the page, or loaded by some + other means into the JS execution environment." + #?(:cljs {:tag boolean}) + [dep] + (let [js-index (:js-dependency-index @env/*compiler*)] + (if-some [[_ {:keys [foreign external?]}] (find js-index (name (-> dep lib&sublib first)))] + (and foreign external?) + false))) + (defn foreign-dep? #?(:cljs {:tag boolean}) [dep] @@ -2828,13 +2839,19 @@ (error env (error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))))))))))) +(defn global-ns? [x] + (or (= 'js x) + (= "js" (namespace x)))) + (defn missing-use? [lib sym cenv] - (let [js-lib (get-in cenv [:js-dependency-index (name lib)])] - (and (= (get-in cenv [::namespaces lib :defs sym] ::not-found) ::not-found) - (not (= (get js-lib :group) :goog)) - (not (get js-lib :closure-lib)) - (not (node-module-dep? lib)) - (not (dep-has-global-exports? lib))))) + ;; ignore globals referred via :refer-global + (when-not (global-ns? lib) + (let [js-lib (get-in cenv [:js-dependency-index (name lib)])] + (and (= (get-in cenv [::namespaces lib :defs sym] ::not-found) ::not-found) + (not (= (get js-lib :group) :goog)) + (not (get js-lib :closure-lib)) + (not (node-module-dep? lib)) + (not (dep-has-global-exports? lib)))))) (defn missing-rename? [sym cenv] (let [lib (symbol (namespace sym)) @@ -3047,6 +3064,90 @@ ret (recur fs ret true))))) +(defn parse-global-refer-spec + [env args] + (let [xs (filter #(-> % first (= :refer-global)) args) + cnt (count xs)] + (cond + (> cnt 1) + (throw (error env "Only one :refer-global form is allowed per namespace definition")) + + (== cnt 1) + (let [[_ & {:keys [only rename] :as parsed-spec}] (first xs) + only-set (set only) + err-str "Only (:refer-global :only [names]) and optionally `:rename {from to}` specs supported. + :rename symbols must be present in :only"] + (when-not (or (empty? only) + (and (vector? only) + (every? symbol only))) + (throw (error env err-str))) + (when-not (or (empty? rename) + (and (map? rename) + (every? symbol (mapcat identity rename)) + (every? only-set (keys rename)))) + (throw (error env (str err-str (pr-str parsed-spec))))) + (when-not (every? #{:only :rename} (keys parsed-spec)) + (throw (error env (str err-str (pr-str parsed-spec))))) + {:use (zipmap only (repeat 'js)) + :rename (into {} + (map (fn [[orig new-name]] + [new-name (symbol "js" (str orig))])) + rename)})))) + +(defn parse-global-require-spec + [env cenv deps aliases spec] + (if (or (symbol? spec) (string? spec)) + (recur env cenv deps aliases [spec]) + (do + (basic-validate-ns-spec env false spec) + (let [[lib & opts] spec + {alias :as referred :refer renamed :rename + :or {alias (if (string? lib) + (symbol (munge lib)) + lib)}} + (apply hash-map opts) + referred-without-renamed (seq (remove (set (keys renamed)) referred)) + [rk uk renk] [:require :use :rename]] + (when-not (or (symbol? alias) (nil? alias)) + (throw + (error env + (parse-ns-error-msg spec + ":as must be followed by a symbol in :require / :require-macros")))) + (when (some? alias) + (let [lib' ((:fns @aliases) alias)] + (when (and (some? lib') (not= lib lib')) + (throw (error env (parse-ns-error-msg spec ":as alias must be unique")))) + (when (= alias 'js) + (when-not (= lib (get-in @aliases [:fns 'js])) ; warn only once + (warning :js-used-as-alias env {:spec spec}))) + (swap! aliases update-in [:fns] conj [alias lib]))) + (when-not (or (and (sequential? referred) + (every? symbol? referred)) + (nil? referred)) + (throw + (error env + (parse-ns-error-msg spec + ":refer must be followed by a sequence of symbols in :require / :require-macros")))) + (swap! deps conj lib) + (let [ret (merge + (when (some? alias) + {rk (merge {alias lib} {lib lib})}) + (when (some? referred-without-renamed) + {uk (apply hash-map (interleave referred-without-renamed (repeat lib)))}) + (when (some? renamed) + {renk (reduce (fn [m [original renamed]] + (when-not (some #{original} referred) + (throw (error env + (str "Renamed symbol " original " not referred")))) + (assoc m renamed (symbol (str lib) (str original)))) + {} renamed)}))] + (swap! cenv assoc-in [:js-dependency-index (str lib)] + {:external? true + :foreign true + :provides [(str lib)] + :global-exports {lib lib}}) + ret))))) + (defn parse-require-spec [env macros? deps aliases spec] (if (or (symbol? spec) (string? spec)) (recur env macros? deps aliases [spec]) @@ -3300,6 +3401,10 @@ (select-keys new deep-merge-keys)))) new)) +(def ns-spec-cases + #{:use :use-macros :require :require-macros + :import :refer-global :require-global}) + (defmethod parse 'ns [_ env [_ name & args :as form] _ opts] (when-not *allow-ns* @@ -3334,6 +3439,7 @@ core-renames (reduce (fn [m [original renamed]] (assoc m renamed (symbol "cljs.core" (str original)))) {} core-renames) + {global-uses :use global-renames :rename} (parse-global-refer-spec env args) deps (atom []) ;; as-aliases can only be used *once* because they are about the reader aliases (atom {:fns as-aliases :macros as-aliases}) @@ -3343,8 +3449,9 @@ (partial use->require env)) :use-macros (comp (partial parse-require-spec env true deps aliases) (partial use->require env)) - :import (partial parse-import-spec env deps)} - valid-forms (atom #{:use :use-macros :require :require-macros :import}) + :import (partial parse-import-spec env deps) + :require-global #(parse-global-require-spec env env/*compiler* deps aliases %)} + valid-forms (atom #{:use :use-macros :require :require-macros :require-global :import}) reload (atom {:use nil :require nil :use-macros nil :require-macros nil}) reloads (atom {}) {uses :use requires :require renames :rename @@ -3352,8 +3459,8 @@ rename-macros :rename-macros imports :import :as params} (reduce (fn [m [k & libs :as libspec]] - (when-not (#{:use :use-macros :require :require-macros :import} k) - (throw (error env (str "Only :refer-clojure, :require, :require-macros, :use, :use-macros, and :import libspecs supported. Got " libspec " instead.")))) + (when-not (#{:use :use-macros :require :require-macros :require-global :import} k) + (throw (error env (str "Only :refer-clojure, :require, :require-macros, :use, :use-macros, :require-global and :import libspecs supported. Got " libspec " instead.")))) (when-not (@valid-forms k) (throw (error env (str "Only one " k " form is allowed per namespace definition")))) (swap! valid-forms disj k) @@ -3370,7 +3477,7 @@ (apply merge-with merge m (map (spec-parsers k) (remove #{:reload :reload-all} libs)))) - {} (remove (fn [[r]] (= r :refer-clojure)) args)) + {} (remove (fn [[r]] (#{:refer-clojure :refer-global} r)) args)) ;; patch `require-macros` and `use-macros` in Bootstrap for namespaces ;; that require their own macros #?@(:cljs [[require-macros use-macros] @@ -3392,9 +3499,9 @@ :use-macros use-macros :require-macros require-macros :rename-macros rename-macros - :uses uses + :uses (merge uses global-uses) :requires requires - :renames (merge renames core-renames) + :renames (merge renames core-renames global-renames) :imports imports}] (swap! env/*compiler* update-in [::namespaces name] merge ns-info) (merge {:op :ns @@ -3434,6 +3541,7 @@ core-renames (reduce (fn [m [original renamed]] (assoc m renamed (symbol "cljs.core" (str original)))) {} core-renames) + {global-uses :use global-renames :rename} (parse-global-refer-spec env args) deps (atom []) ;; as-aliases can only be used *once* because they are about the reader aliases (atom {:fns as-aliases :macros as-aliases}) @@ -3443,7 +3551,8 @@ (partial use->require env)) :use-macros (comp (partial parse-require-spec env true deps aliases) (partial use->require env)) - :import (partial parse-import-spec env deps)} + :import (partial parse-import-spec env deps) + :require-global #(parse-global-require-spec env env/*compiler* deps aliases %)} reload (atom {:use nil :require nil :use-macros nil :require-macros nil}) reloads (atom {}) {uses :use requires :require renames :rename @@ -3464,7 +3573,7 @@ (apply merge-with merge m (map (spec-parsers k) (remove #{:reload :reload-all} libs)))) - {} (remove (fn [[r]] (= r :refer-clojure)) args))] + {} (remove (fn [[r]] (#{:refer-clojure :refer-global} r)) args))] (set! *cljs-ns* name) (let [require-info {:as-aliases as-aliases @@ -3473,9 +3582,9 @@ :use-macros use-macros :require-macros require-macros :rename-macros rename-macros - :uses uses + :uses (merge uses global-uses) :requires requires - :renames (merge renames core-renames) + :renames (merge renames core-renames global-renames) :imports imports}] (swap! env/*compiler* update-in [::namespaces name] merge-ns-info require-info env) (merge {:op :ns* diff --git a/src/main/clojure/cljs/closure.clj b/src/main/clojure/cljs/closure.clj index 550fd68d5..2c923d85c 100644 --- a/src/main/clojure/cljs/closure.clj +++ b/src/main/clojure/cljs/closure.clj @@ -1127,13 +1127,17 @@ (let [requires (set (mapcat deps/-requires inputs)) required-js (js-dependencies opts requires)] (concat - (map - (fn [{:keys [foreign url file provides requires] :as js-map}] - (let [url (or url (io/resource file))] - (merge - (javascript-file foreign url provides requires) - js-map))) - required-js) + (->> required-js + ;; :foreign-libs which declare :external? have no sources (they are included + ;; on the page via some script tag we'll never see). :require-global libs are + ;; implicit :foreign-libs where :external? is true + (remove :external?) + (map + (fn [{:keys [foreign url file provides requires] :as js-map}] + (let [url (or url (io/resource file))] + (merge + (javascript-file foreign url provides requires) + js-map))))) (when (-> @env/*compiler* :options :emit-constants) [(constants-javascript-file opts)]) inputs))) @@ -1604,7 +1608,11 @@ "], [" ;; even under Node.js where runtime require is possible ;; this is necessary - see CLJS-2151 - (ns-list (cond->> (deps/-requires input) + (ns-list (cond->> + ;; remove external? foreign deps - they are already loaded + ;; in the environment, there is nothing to do. + ;; :require-global is the typical case here + (remove ana/external-dep? (deps/-requires input)) ;; under Node.js we emit native `require`s for these (= :nodejs (:target opts)) (filter (complement ana/node-module-dep?)))) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index 522a88357..3e0d42480 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -1367,7 +1367,10 @@ escape-string wrap-in-double-quotes) ");")) - (emitln "goog.require('" (munge lib) "');"))))] + (if-not (ana/external-dep? lib) + (emitln "goog.require('" (munge lib) "');") + ;; TODO: validate the lib exists + ))))] :cljs [(and (ana/foreign-dep? lib) (not (keyword-identical? optimizations :none))) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index b326a3fa6..a58ec944f 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -12,7 +12,7 @@ defprotocol defrecord defstruct deftype delay destructure doseq dosync dotimes doto extend-protocol extend-type fn for future gen-class gen-interface if-let if-not import io! lazy-cat lazy-seq let letfn locking loop - memfn ns or proxy proxy-super pvalues refer-clojure reify sync time + memfn ns or proxy proxy-super pvalues reify sync time when when-first when-let when-not while with-bindings with-in-str with-loading-context with-local-vars with-open with-out-str with-precision with-redefs satisfies? identical? true? false? number? nil? instance? symbol? keyword? string? str get @@ -3121,6 +3121,20 @@ [& args] `(~'ns* ~(cons :refer-clojure args))) +(core/defmacro refer-global + "Refer global js vars. Supports renaming via :rename. + + (refer-global :only '[Date Symbol] :rename '{Symbol Sym})" + [& args] + `(~'ns* ~(cons :refer-global args))) + +(core/defmacro require-global + "Require libraries in the global JS environment. + + (require-global '[SomeLib :as lib :refer [foo]])" + [& args] + `(~'ns* ~(cons :require-global args))) + ;; INTERNAL - do not use, only for Node.js (core/defmacro load-file* [f] `(goog/nodeGlobalRequire ~f)) diff --git a/src/main/clojure/cljs/repl.cljc b/src/main/clojure/cljs/repl.cljc index b685a62e5..85117c725 100644 --- a/src/main/clojure/cljs/repl.cljc +++ b/src/main/clojure/cljs/repl.cljc @@ -256,7 +256,12 @@ ([repl-env requires] (load-dependencies repl-env requires nil)) ([repl-env requires opts] - (doall (mapcat #(load-namespace repl-env % opts) (distinct requires))))) + (->> requires + distinct + (remove ana/global-ns?) + (remove ana/external-dep?) + (mapcat #(load-namespace repl-env % opts)) + doall))) (defn ^File js-src->cljs-src "Map a JavaScript output file back to the original ClojureScript source @@ -652,7 +657,7 @@ (defn- wrap-fn [form] (cond (and (seq? form) - (#{'ns 'require 'require-macros + (#{'ns 'require 'require-macros 'refer-global 'require-global 'use 'use-macros 'import 'refer-clojure} (first form))) identity @@ -673,7 +678,7 @@ (defn- init-wrap-fn [form] (cond (and (seq? form) - (#{'ns 'require 'require-macros + (#{'ns 'require 'require-macros 'refer-global 'use 'use-macros 'import 'refer-clojure} (first form))) identity diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index f1b639938..ca388182d 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -171,7 +171,7 @@ (analyze ns-env '(ns foo.bar (:unless []))) (catch Exception e (.getMessage (.getCause e)))) - "Only :refer-clojure, :require, :require-macros, :use, :use-macros, and :import libspecs supported. Got (:unless []) instead.")) + "Only :refer-clojure, :require, :require-macros, :use, :use-macros, :require-global and :import libspecs supported. Got (:unless []) instead.")) (is (.startsWith (try (analyze ns-env '(ns foo.bar (:require baz.woz) (:require noz.goz))) @@ -387,6 +387,32 @@ :renames {map clj-map}})) (is (set? (:excludes parsed))))) +(deftest test-parse-global-refer + (let [parsed (ana/parse-global-refer-spec {} + '((:refer-global :only [Date Symbol] :rename {Symbol JSSymbol})))] + (is (= parsed + '{:use {Date js Symbol js} + :rename {JSSymbol js/Symbol}})))) + +(deftest test-parse-require-global + (let [cenv (atom {}) + deps (atom []) + parsed (ana/parse-global-require-spec {} cenv deps (atom {:fns {}}) + '[React :refer [createElement] :as react])] + (println (pr-str @cenv) (pr-str @deps)) + (is (= parsed + '{:require {react React + React React} + :use {createElement React}}))) + (let [cenv (atom {}) + deps (atom []) + parsed (ana/parse-global-require-spec {} cenv deps (atom {:fns {}}) + '[React :refer [createElement] :rename {createElement create} :as react])] + (is (= parsed + '{:require {react React + React React} + :rename {create React/createElement}})))) + (deftest test-cljs-1785-js-shadowed-by-local (let [ws (atom [])] (ana/with-warning-handlers [(collecting-warning-handler ws)] @@ -547,6 +573,14 @@ (analyze test-env '(map #(require '[clojure.set :as set]) [1 2])))))) +(deftest test-analyze-refer-global + (testing "refer-global macro expr return expected AST" + (binding [ana/*cljs-ns* ana/*cljs-ns* + ana/*cljs-warnings* nil] + (let [test-env (ana/empty-env)] + (is (= (-> (analyze test-env '(refer-global :only '[Date])) :uses vals set) + '#{js})))))) + (deftest test-gen-user-ns ;; note: can't use `with-redefs` because direct-linking is enabled (let [s "src/cljs/foo.cljs" @@ -1533,3 +1567,27 @@ (ana/gen-constant-id '+))) (is (not= (ana/gen-constant-id 'foo.bar) (ana/gen-constant-id 'foo$bar)))) + +;; ----------------------------------------------------------------------------- +;; :refer-global / :require-global ns parsing tests + +(deftest test-refer-global + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (let [parsed-ns (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:refer-global :only [Date] :rename {Date MyDate}))))] + (= (:renames parsed-ns) + '{MyDate js/Date})))) + +(deftest test-require-global + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (let [parsed-ns (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:require-global [React :as react :refer [createElement]]))))] + (is (= (:requires parsed-ns) + '{React React + react React})) + (is (= (:uses parsed-ns) + '{createElement React}))))) From e86a369d7f99a43e5745e7b425f199d7966be426 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Fri, 10 Oct 2025 20:56:38 -0400 Subject: [PATCH 32/94] runtime existence check for :require-global libs under :optimizations :none (#270) --- src/main/clojure/cljs/compiler.cljc | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index 3e0d42480..8a17d17d2 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -1312,19 +1312,24 @@ (apply str (map #(str "['" % "']") xs))))) -(defn emit-global-export [ns-name global-exports lib] - (let [[lib' sublib] (ana/lib&sublib lib)] +(defn emit-global-export [ns-name global-exports lib opts] + (let [[lib' sublib] (ana/lib&sublib lib) + ref (str "goog.global" + ;; Convert object dot access to bracket access + (->> (string/split (name (or (get global-exports (symbol lib')) + (get global-exports (name lib')))) + #"\.") + (map (fn [prop] (str "[\"" prop "\"]"))) + (apply str)))] + (when (and (ana/external-dep? lib') + (= :none (:optimizations opts))) + (emitln + "if(!" ref ") throw new Error(\"External library, " lib' ", never provided\");")) (emitln (munge ns-name) "." (ana/munge-global-export lib) - " = goog.global" - ;; Convert object dot access to bracket access - (->> (string/split (name (or (get global-exports (symbol lib')) - (get global-exports (name lib')))) - #"\.") - (map (fn [prop] - (str "[\"" prop "\"]"))) - (apply str)) + " = " + ref (sublib-select sublib) ";"))) @@ -1409,7 +1414,7 @@ ;; Global Exports (doseq [lib global-exports-libs] (let [{:keys [global-exports]} (get js-dependency-index (name (-> lib ana/lib&sublib first)))] - (emit-global-export ns-name global-exports lib))) + (emit-global-export ns-name global-exports lib options))) (when (-> libs meta :reload-all) (emitln "if(!COMPILED) " loaded-libs " = cljs.core.into(" loaded-libs-temp ", " loaded-libs ");")))) From e46a486566645e6e274798342b554eac7011cd70 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Fri, 10 Oct 2025 21:37:21 -0400 Subject: [PATCH 33/94] test case for reported :lite-mode bug (#271) - can't repro yet --- src/test/cljs/cljs/lite_collections_test.cljs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/cljs/cljs/lite_collections_test.cljs b/src/test/cljs/cljs/lite_collections_test.cljs index f481be9ff..16d0296c1 100644 --- a/src/test/cljs/cljs/lite_collections_test.cljs +++ b/src/test/cljs/cljs/lite_collections_test.cljs @@ -24,6 +24,10 @@ (is (= (simple-set [(MapEntry. 1 2 nil)]) (set [(MapEntry. 1 2 nil)])))) +(deftest test-obj-map-clj->js + (= 1 (aget (clj->js (obj-map :x 1)) "x")) + (= 1 (aget (clj->js {:x 1}) "x"))) + (comment (require '[cljs.lite-collections-test] :reload) From db5cbfd68849f9633f8c3d6c20143ba6a3cc81c6 Mon Sep 17 00:00:00 2001 From: Paula Gearon Date: Tue, 14 Oct 2025 08:14:44 -0400 Subject: [PATCH 34/94] CLJS-3454: New set instances are created when redundant data is added --- src/main/cljs/cljs/core.cljs | 20 ++++++++++++++++---- src/test/cljs/cljs/core_test.cljs | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 27464f4f0..1e96c24f6 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -9304,7 +9304,10 @@ reduces them without incurring seq initialization" ICollection (-conj [coll o] - (PersistentHashSet. meta (assoc hash-map o nil) nil)) + (let [m (-assoc hash-map o nil)] + (if (identical? m hash-map) + coll + (PersistentHashSet. meta m nil)))) IEmptyableCollection (-empty [coll] (-with-meta (.-EMPTY PersistentHashSet) meta)) @@ -9341,7 +9344,10 @@ reduces them without incurring seq initialization" ISet (-disjoin [coll v] - (PersistentHashSet. meta (-dissoc hash-map v) nil)) + (let [m (-dissoc hash-map v)] + (if (identical? m hash-map) + coll + (PersistentHashSet. meta m nil)))) IFn (-invoke [coll k] @@ -9459,7 +9465,10 @@ reduces them without incurring seq initialization" ICollection (-conj [coll o] - (PersistentTreeSet. meta (assoc tree-map o nil) nil)) + (let [m (-assoc tree-map o nil)] + (if (identical? m tree-map) + coll + (PersistentTreeSet. meta m nil)))) IEmptyableCollection (-empty [coll] (PersistentTreeSet. meta (-empty tree-map) 0)) @@ -9513,7 +9522,10 @@ reduces them without incurring seq initialization" ISet (-disjoin [coll v] - (PersistentTreeSet. meta (dissoc tree-map v) nil)) + (let [m (-dissoc tree-map v)] + (if (identical? m tree-map) + coll + (PersistentTreeSet. meta m nil)))) IFn (-invoke [coll k] diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index d92fb5105..9ce1a9976 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -737,6 +737,27 @@ (is (= #{1 2} (hash-set 1 2 2))) (is (= #{1 2} (apply hash-set [1 2 2])))) +(deftest test-ordered-set + (is (= #{1 2} (sorted-set 1 2 2))) + (is (= [1 2 3] (seq (sorted-set 2 3 1)))) + (is (= #{1 2} (apply sorted-set [1 2 2])))) + +(deftest test-3454-conj + (is (= #{1 2 3} (conj #{1 2} 3))) + (is (= #{1 2 3} (conj (sorted-set 1 2) 3))) + (let [s #{1 2} + ss (sorted-set 1 2)] + (is (identical? s (conj s 2))) + (is (identical? ss (conj ss 2))))) + +(deftest test-3454-disj + (is (= #{1 2} (disj #{1 2 3} 3))) + (is (= #{1 2} (disj (sorted-set 1 2 3) 3))) + (let [s #{1 2} + ss (sorted-set 1 2)] + (is (identical? s (disj s 3))) + (is (identical? ss (disj ss 3))))) + (deftest test-585 (is (= (last (map identity (into [] (range 32)))) 31)) (is (= (into #{} (range 32)) From 6cb2c4dfa971abd56b2b1acee5f1eb97fda9bac1 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Tue, 14 Oct 2025 20:27:19 -0400 Subject: [PATCH 35/94] CLJS-3452: optimize str by compiling to + / .toString + compile time optimizations (#273) - Avoiding Array.join in favor of + gives around 4x perf boost - Avoiding 1-arity str calls for compile time constants Co-authored-by: Michiel Borkent --- src/main/cljs/cljs/core.cljs | 2 +- src/main/clojure/cljs/core.cljc | 36 +++++++++++-------- src/test/cljs/cljs/core_test.cljs | 8 +++++ .../cljs_3452_str_optimizations/core.cljs | 9 +++++ src/test/clojure/cljs/build_api_tests.clj | 18 ++++++++++ 5 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 src/test/cljs_build/cljs_3452_str_optimizations/core.cljs 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)))) From a761d54e43ae1f6b04a8373cc587648252cad55e Mon Sep 17 00:00:00 2001 From: David Nolen Date: Fri, 17 Oct 2025 17:32:13 -0400 Subject: [PATCH 36/94] CLJS-2471: ChunkedSeq should implemented ICounted (#275) Co-authored-by: Roman Liutikov --- src/main/cljs/cljs/core.cljs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index c87b75179..3515da790 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -6002,6 +6002,10 @@ reduces them without incurring seq initialization" (-empty [coll] ()) + ICounted + (-count [coll] + (- (-count vec) (+ i off))) + IChunkedSeq (-chunked-first [coll] (array-chunk node off)) From 249faa2a584f9a41a39d6fcd07712e1ac414720a Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 19 Oct 2025 11:31:47 -0400 Subject: [PATCH 37/94] CLJS-3457: `:lite-mode` data structures should satisfy `identical?` if unchanged by `conj` (#276) --- src/main/cljs/cljs/core.cljs | 27 ++++++++++++------- src/test/cljs/cljs/lite_collections_test.cljs | 8 ++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 3515da790..3beaa425c 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12603,10 +12603,14 @@ reduces them without incurring seq initialization" (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] (if (string? k) (if-not (nil? (scan-array 1 k strkeys)) - (let [new-strobj (obj-clone strobj strkeys)] - (gobject/set new-strobj k v) - (ObjMap. meta strkeys new-strobj nil)) ;overwrite - (let [new-strobj (obj-clone strobj strkeys) ; append + (if (identical? v (gobject/get strobj k)) + coll + ; overwrite + (let [new-strobj (obj-clone strobj strkeys)] + (gobject/set new-strobj k v) + (ObjMap. meta strkeys new-strobj nil))) + ; append + (let [new-strobj (obj-clone strobj strkeys) new-keys (aclone strkeys)] (gobject/set new-strobj k v) (.push new-keys k) @@ -12812,10 +12816,12 @@ reduces them without incurring seq initialization" i (scan-array-equiv 2 k new-bucket)] (aset new-hashobj h new-bucket) (if (some? i) - (do - ; found key, replace - (aset new-bucket (inc i) v) - (HashMap. meta count new-hashobj nil)) + (if (identical? v (aget new-bucket (inc i))) + coll + (do + ; found key, replace + (aset new-bucket (inc i) v) + (HashMap. meta count new-hashobj nil))) (do ; did not find key, append (.push new-bucket k v) @@ -12954,7 +12960,10 @@ reduces them without incurring seq initialization" ICollection (-conj [coll o] - (Set. meta (assoc hash-map o o) nil)) + (let [new-hash-map (assoc hash-map o o)] + (if (identical? new-hash-map hash-map) + coll + (Set. meta new-hash-map nil)))) IEmptyableCollection (-empty [coll] (with-meta (. Set -EMPTY) meta)) diff --git a/src/test/cljs/cljs/lite_collections_test.cljs b/src/test/cljs/cljs/lite_collections_test.cljs index 16d0296c1..a96148f00 100644 --- a/src/test/cljs/cljs/lite_collections_test.cljs +++ b/src/test/cljs/cljs/lite_collections_test.cljs @@ -28,6 +28,14 @@ (= 1 (aget (clj->js (obj-map :x 1)) "x")) (= 1 (aget (clj->js {:x 1}) "x"))) +(deftest test-unchanged-identical? + (let [m (obj-map :foo 1)] + (identical? m (assoc m :foo 1))) + (let [m (simple-hash-map :foo 1)] + (identical? m (assoc m :foo 1))) + (let [s (simple-set [:foo])] + (identical? s (conj s :foo)))) + (comment (require '[cljs.lite-collections-test] :reload) From 4eff03dd809120578326573e5de325dd67cc4885 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 19 Oct 2025 11:41:10 -0400 Subject: [PATCH 38/94] add missing simple Set disjoin identical? check --- src/main/cljs/cljs/core.cljs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index d889a7ca9..82bd82c6b 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -13014,7 +13014,10 @@ reduces them without incurring seq initialization" ISet (-disjoin [coll v] - (Set. meta (-dissoc hash-map v) nil)) + (let [new-hash-map (-dissoc hash-map v)] + (if (identical? new-hash-map hash-map) + coll + (Set. meta new-hash-map nil)))) IEditableCollection (-as-transient [coll] From d912f929e4ca9b486a182575659dde95be20c386 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 19 Oct 2025 11:55:14 -0400 Subject: [PATCH 39/94] cljs.analyzer/global-ns? should check that x is a symbol first --- src/main/clojure/cljs/analyzer.cljc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 924ac3477..af28ca2eb 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -2840,8 +2840,9 @@ (error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))))))))))) (defn global-ns? [x] - (or (= 'js x) - (= "js" (namespace x)))) + (and (symbol? x) + (or (= 'js x) + (= "js" (namespace x))))) (defn missing-use? [lib sym cenv] ;; ignore globals referred via :refer-global From 075edcca59b4863d6fe04c55703e3fbeaaa61bbc Mon Sep 17 00:00:00 2001 From: David Nolen Date: Tue, 4 Nov 2025 14:35:31 -0500 Subject: [PATCH 40/94] add :lite-mode & :elide-to-string to cljs.analyzer/build-affecting-options --- src/main/clojure/cljs/analyzer.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index af28ca2eb..321489c30 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -4832,7 +4832,7 @@ (defn build-affecting-options [opts] (select-keys opts [:static-fns :fn-invoke-direct :optimize-constants :elide-asserts :target :nodejs-rt - :cache-key :checked-arrays :language-out :optimizations]))) + :cache-key :checked-arrays :language-out :optimizations :lite-mode :elide-to-string]))) #?(:clj (defn build-affecting-options-sha [path opts] From b69da0eee62551f3fcc6c05eddaa6f27f2e89c63 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sat, 8 Nov 2025 09:28:20 -0500 Subject: [PATCH 41/94] don't add the imul.js preamble in lite-mode (#277) --- src/main/clojure/cljs/closure.clj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/cljs/closure.clj b/src/main/clojure/cljs/closure.clj index 2c923d85c..3f20a25bb 100644 --- a/src/main/clojure/cljs/closure.clj +++ b/src/main/clojure/cljs/closure.clj @@ -2525,8 +2525,10 @@ :ups-foreign-libs (expand-libs foreign-libs) :ups-externs externs :emit-constants emit-constants - :cache-analysis-format (:cache-analysis-format opts :transit)) - (update-in [:preamble] #(into (or % []) ["cljs/imul.js"]))) + :cache-analysis-format (:cache-analysis-format opts :transit))) + + (not (:lite-mode opts)) + (update-in [:preamble] #(into (or % []) ["cljs/imul.js"])) (:lite-mode opts) (assoc-in [:closure-defines (str (comp/munge 'cljs.core/LITE_MODE))] From c3c3773755d9c5b7454c36c4aeabece3fe6f7fbe Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sat, 8 Nov 2025 11:47:04 -0500 Subject: [PATCH 42/94] CLJS-3461: don't hard-code destructuring to PAM (#278) - update `--destructure-map`, `seq-to-map-for-destructuring` for `:lite-mode` - `ObjMap.createAsIfByAssoc`, perf is not critical for `:lite-mode` opt for simplicity for now - add REPL aliases to deps.edn to make it easier to play around with `:lite-mode` --- deps.edn | 6 ++++- src/main/cljs/cljs/core.cljs | 51 ++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/deps.edn b/deps.edn index e3a236c2a..5233627bc 100644 --- a/deps.edn +++ b/deps.edn @@ -9,7 +9,11 @@ org.clojure/tools.reader {:mvn/version "1.3.6"} org.clojure/test.check {:mvn/version "1.1.1"}} :aliases - {:cli.test.run {:extra-paths ["src/test/cljs_cli"] + {:cljs-repl {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-re" "node" "-d" ".cljs_repl" "-r"]} + :cljs-lite-repl {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-co" "{:lite-mode true}" "-re" "node" "-d" ".cljs_lite_repl" "-r"]} + :cli.test.run {:extra-paths ["src/test/cljs_cli"] :main-opts ["-i" "src/test/cljs_cli/cljs_cli/test_runner.clj" "-e" "(cljs-cli.test-runner/-main)"]} :compiler.test {:extra-paths ["src/test/cljs" "src/test/cljs_build" "src/test/cljs_cp" diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 82bd82c6b..707a7f0cd 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -4124,16 +4124,26 @@ reduces them without incurring seq initialization" (set! *unchecked-if* false) +(declare ObjMap) + ;; CLJS-3200: used by destructure macro for maps to reduce amount of repeated code ;; placed here because it needs apply and hash-map (only declared at this point) (defn --destructure-map [gmap] - (if (implements? ISeq gmap) - (if (next gmap) - (.createAsIfByAssoc PersistentArrayMap (to-array gmap)) - (if (seq gmap) - (first gmap) - (.-EMPTY PersistentArrayMap))) - gmap)) + (if ^boolean LITE_MODE + (if (implements? ISeq gmap) + (if (next gmap) + (.createAsIfByAssoc ObjMap (to-array gmap)) + (if (seq gmap) + (first gmap) + (.-EMPTY ObjMap))) + gmap) + (if (implements? ISeq gmap) + (if (next gmap) + (.createAsIfByAssoc PersistentArrayMap (to-array gmap)) + (if (seq gmap) + (first gmap) + (.-EMPTY PersistentArrayMap))) + gmap))) (defn vary-meta "Returns an object of the same type and value as obj, with @@ -7126,7 +7136,7 @@ reduces them without incurring seq initialization" (fn [init] ;; check trailing element (let [len (alength init) - has-trailing? (== 1 (bit-and len 1))] + has-trailing? (== 1 (bit-and len 1))] (if-not (or has-trailing? (pam-dupes? init)) (PersistentArrayMap. nil (/ len 2) init nil) (.createAsIfByAssocComplexPath PersistentArrayMap init has-trailing?))))) @@ -9039,9 +9049,13 @@ reduces them without incurring seq initialization" "Builds a map from a seq as described in https://clojure.org/reference/special_forms#keyword-arguments" [s] - (if (next s) - (.createAsIfByAssoc PersistentArrayMap (to-array s)) - (if (seq s) (first s) (.-EMPTY PersistentArrayMap)))) + (if ^boolean LITE_MODE + (if (next s) + (.createAsIfByAssoc ObjMap (to-array s)) + (if (seq s) (first s) (.-EMPTY ObjMap))) + (if (next s) + (.createAsIfByAssoc PersistentArrayMap (to-array s)) + (if (seq s) (first s) (.-EMPTY PersistentArrayMap))))) (defn sorted-map "keyval => key val @@ -12731,6 +12745,21 @@ reduces them without incurring seq initialization" (recur (nnext kvs))) (.fromObject ObjMap ks obj))))) +(set! (. ObjMap -createAsIfByAssoc) + (fn [init] + ;; check trailing element + (let [len (alength init) + has-trailing? (== 1 (bit-and len 1)) + init (if has-trailing? + (pam-grow-seed-array init + (into {} (aget init (dec len)))) + init) + len (alength init)] + (loop [i 0 ret {}] + (if (< i len) + (recur (+ i 2) (assoc ret (aget init i) (aget init (inc i)))) + ret))))) + (defn- scan-array-equiv [incr k array] (let [len (alength array)] (loop [i 0] From acaefa15e6adfc92d32ead7e882d1f0e73ecd4a7 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sat, 8 Nov 2025 11:49:11 -0500 Subject: [PATCH 43/94] Fix up missing "sources" in advanced compiled source map (#251) --- src/main/clojure/cljs/closure.clj | 33 ++++++++++++----------- src/test/cljs_build/adv_src_map/core.cljs | 3 +++ src/test/clojure/cljs/build_api_tests.clj | 27 +++++++++++++++++++ 3 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 src/test/cljs_build/adv_src_map/core.cljs diff --git a/src/main/clojure/cljs/closure.clj b/src/main/clojure/cljs/closure.clj index 3f20a25bb..05fa761aa 100644 --- a/src/main/clojure/cljs/closure.clj +++ b/src/main/clojure/cljs/closure.clj @@ -1394,11 +1394,12 @@ (let [source (first sources)] (recur (next sources) - (let [{:keys [provides source-url]} source] - (if (and provides source-url) + (let [{:keys [provides]} source + url (or (:source-url source) (:url source))] + (if (and provides url) (assoc relpaths - (.getPath ^URL source-url) - (util/ns->relpath (first provides) (util/ext source-url))) + (.getPath ^URL url) + (util/ns->relpath (first provides) (util/ext url))) relpaths)) (if-let [url (:url source)] (let [path (.getPath ^URL url)] @@ -1415,19 +1416,19 @@ (spit (io/file name) (sm/encode merged - {:preamble-line-count (+ (:preamble-line-count opts 0) - (:foreign-deps-line-count opts 0)) - :lines (+ (:lineCount sm-json) - (:preamble-line-count opts 0) - (:foreign-deps-line-count opts 0) - 2) - :file name - :output-dir (util/output-directory opts) - :source-map (:source-map opts) - :source-map-path (:source-map-path opts) - :source-map-timestamp (:source-map-timestamp opts) + {:preamble-line-count (+ (:preamble-line-count opts 0) + (:foreign-deps-line-count opts 0)) + :lines (+ (:lineCount sm-json) + (:preamble-line-count opts 0) + (:foreign-deps-line-count opts 0) + 2) + :file name + :output-dir (util/output-directory opts) + :source-map (:source-map opts) + :source-map-path (:source-map-path opts) + :source-map-timestamp (:source-map-timestamp opts) :source-map-pretty-print (:source-map-pretty-print opts) - :relpaths relpaths})))))) + :relpaths relpaths})))))) (defn write-variable-maps [^Result result opts] (let [var-out (:closure-variable-map-out opts)] diff --git a/src/test/cljs_build/adv_src_map/core.cljs b/src/test/cljs_build/adv_src_map/core.cljs new file mode 100644 index 000000000..22cc32cab --- /dev/null +++ b/src/test/cljs_build/adv_src_map/core.cljs @@ -0,0 +1,3 @@ +(ns adv-src-map.core) + +(.log js/console "Hello!" (first [1 2 3])) \ No newline at end of file diff --git a/src/test/clojure/cljs/build_api_tests.clj b/src/test/clojure/cljs/build_api_tests.clj index 5c6ce522e..3e44d1b4b 100644 --- a/src/test/clojure/cljs/build_api_tests.clj +++ b/src/test/clojure/cljs/build_api_tests.clj @@ -958,3 +958,30 @@ (testing "only two 1-arity str calls, compile time constants are optimized" (is (= 2 (count (re-seq #"\$1\(.*?\)" source)))))) (test/delete-out-files out)))) + +#_(deftest test-advanced-source-maps + (testing "Test that the `sources` of the final merged source map matches the + one in the original Closure Compiler generated advanced source map" + (let [out (.getPath (io/file (test/tmp-dir) "adv-src-map"))] + (test/delete-out-files out) + (test/delete-node-modules) + (let [{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'cljs-3346-as-alias.core + :output-to (.getPath (io/file out "main.js")) + :source-map (.getPath (io/file out "main.js.map")) + :output-dir out + :optimizations :advanced + :closure-source-map true}} + cenv (env/default-compiler-env)] + (build/build (build/inputs (io/file inputs "adv_src_map/core.cljs")) opts cenv)) + (let [cljs-src-map (->> (io/file out "main.js.map") slurp json/read-str) + closure-src-map (->> (io/file out "main.js.map.closure") slurp json/read-str)] + (println (get closure-src-map "sources")) + (println (get cljs-src-map "sources"))) + (test/delete-out-files out)))) + +#_(comment + + (clojure.test/test-vars [#'test-advanced-source-maps]) + + ) \ No newline at end of file From bed630170d1468d10bcda7106d11191213b170ca Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sat, 8 Nov 2025 18:51:52 -0500 Subject: [PATCH 44/94] unify (cljs.repl/doc ...) handling of js/console (#279) - just handle everything in the final resolve branch - fixes bug where (doc console) did not work after a (refer-global ...) --- src/main/clojure/cljs/repl.cljc | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/clojure/cljs/repl.cljc b/src/main/clojure/cljs/repl.cljc index 85117c725..919bd54ff 100644 --- a/src/main/clojure/cljs/repl.cljc +++ b/src/main/clojure/cljs/repl.cljc @@ -1452,11 +1452,6 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) (keyword? name) `(cljs.repl/print-doc {:spec ~name :doc (cljs.spec.alpha/describe ~name)}) - (= "js" (namespace name)) - `(cljs.repl/print-doc - (quote ~(merge (select-keys (ana-api/resolve-extern name) [:doc :arglists]) - {:name name}))) - (ana-api/find-ns name) `(cljs.repl/print-doc (quote ~(select-keys (ana-api/find-ns name) [:name :doc]))) @@ -1464,8 +1459,14 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) (ana-api/resolve &env name) `(cljs.repl/print-doc (quote ~(let [var (ana-api/resolve &env name) - m (select-keys var - [:ns :name :doc :forms :arglists :macro :url])] + ns (-> var :name namespace) + m (cond-> var + (= "js" ns) + (-> :name ana-api/resolve-extern + (select-keys [:doc :arglists]) + (merge {:name name})) + (not= "js" ns) + (select-keys [:ns :name :doc :forms :arglists :macro :url]))] (cond-> (update-in m [:name] clojure.core/name) (:protocol-symbol var) (assoc :protocol true From ceba37b5a8513ca837f0d6939681b2c05df32e1c Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 9 Nov 2025 12:01:10 -0500 Subject: [PATCH 45/94] fix typo to make Vector es6-iterable --- src/main/cljs/cljs/core.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 707a7f0cd..8d05e8a7f 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12477,7 +12477,7 @@ reduces them without incurring seq initialization" IPrintWithWriter (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll))) -(es6-iterable PersistentVector) +(es6-iterable Vector) (set! (. Vector -EMPTY) (Vector. nil (array) nil)) From d9365bf51a3684b48a7e4fb3e374bdd6eba17db3 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 9 Nov 2025 20:15:54 -0500 Subject: [PATCH 46/94] remove Vector dep on LazySeq (#280) --- src/main/cljs/cljs/core.cljs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 8d05e8a7f..c2319c699 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12361,12 +12361,7 @@ reduces them without incurring seq initialization" ISeqable (-seq [coll] (when (> (alength array) 0) - (let [vector-seq - (fn vector-seq [i] - (lazy-seq - (when (< i (alength array)) - (cons (aget array i) (vector-seq (inc i))))))] - (vector-seq 0)))) + (prim-seq array))) ICounted (-count [coll] (alength array)) From 10fc535d91f4e0e84c86a4c3e4ded3fec77a1d2b Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 9 Nov 2025 20:54:17 -0500 Subject: [PATCH 47/94] remove hash-map dep on map (#281) --- src/main/cljs/cljs/core.cljs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index c2319c699..507b13f3a 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12773,11 +12773,13 @@ reduces them without incurring seq initialization" (toString [coll] (pr-str* coll)) (keys [coll] - (es6-iterator (map #(-key %) (-seq coll)))) + (let [arr (. (-seq coll) -arr)] + (es6-iterator (prim-seq (.map arr -key (-seq coll)))))) (entries [coll] (es6-entries-iterator (-seq coll))) (values [coll] - (es6-iterator (map #(-val %) (-key coll)))) + (let [arr (. (-seq coll) -arr)] + (es6-iterator (prim-seq (.map arr -val (-seq coll)))))) (has [coll k] (contains? coll k)) (get [coll k not-found] From 274701280de787b598071460cdac0a3709a5bc11 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Tue, 11 Nov 2025 16:47:45 -0500 Subject: [PATCH 48/94] CLJS-3425: Incorrect handling of ##NaN with min/max (#282) - prefix inlining min/max as unchecked - fix min and max fns to do what Clojure does --- src/main/cljs/cljs/core.cljs | 40 +++++++++++++++++++------------ src/main/clojure/cljs/core.cljc | 20 +++++++++------- src/test/cljs/cljs/core_test.cljs | 7 ++++++ 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 507b13f3a..af6cb080c 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -1608,7 +1608,7 @@ reduces them without incurring seq initialization" -1 (loop [idx (cond (pos? start) start - (neg? start) (max 0 (+ start len)) + (neg? start) (unchecked-max 0 (+ start len)) :else start)] (if (< idx len) (if (= (nth coll idx) x) @@ -1624,7 +1624,7 @@ reduces them without incurring seq initialization" (if (zero? len) -1 (loop [idx (cond - (pos? start) (min (dec len) start) + (pos? start) (unchecked-min (dec len) start) (neg? start) (+ len start) :else start)] (if (>= idx 0) @@ -1695,7 +1695,7 @@ reduces them without incurring seq initialization" ICounted (-count [_] - (max 0 (- (alength arr) i))) + (unchecked-max 0 (- (alength arr) i))) IIndexed (-nth [coll n] @@ -2801,17 +2801,32 @@ reduces them without incurring seq initialization" :added "1.11.10"} [a] (Math/abs a)) +(defn NaN? + "Returns true if num is NaN, else false" + [val] + (js/isNaN val)) + (defn ^number max "Returns the greatest of the nums." ([x] x) - ([x y] (cljs.core/max x y)) + ([x y] + (cond + (NaN? x) x + (NaN? y) y + (> x y) x + :else y)) ([x y & more] (reduce max (cljs.core/max x y) more))) (defn ^number min "Returns the least of the nums." ([x] x) - ([x y] (cljs.core/min x y)) + ([x y] + (cond + (NaN? x) x + (NaN? y) y + (< x y) x + :else y)) ([x y & more] (reduce min (cljs.core/min x y) more))) @@ -6156,7 +6171,7 @@ reduces them without incurring seq initialization" (let [v-pos (+ start n)] (if (or (neg? n) (<= (inc end) v-pos)) (throw (js/Error. (str_ "Index " n " out of bounds [0," (-count coll) "]"))) - (build-subvec meta (assoc v v-pos val) start (max end (inc v-pos)) nil)))) + (build-subvec meta (assoc v v-pos val) start (unchecked-max end (inc v-pos)) nil)))) IReduce (-reduce [coll f] @@ -9924,7 +9939,7 @@ reduces them without incurring seq initialization" IChunkedSeq (-chunked-first [rng] - (IntegerRangeChunk. start step (min cnt 32))) + (IntegerRangeChunk. start step (unchecked-min cnt 32))) (-chunked-rest [rng] (if (<= cnt 32) () @@ -10326,7 +10341,7 @@ reduces them without incurring seq initialization" (cons match-vals (lazy-seq (let [post-idx (+ (.-index matches) - (max 1 (.-length match-str)))] + (unchecked-max 1 (.-length match-str)))] (when (<= post-idx (.-length s)) (re-seq* re (subs s post-idx))))))))) @@ -12163,11 +12178,6 @@ reduces them without incurring seq initialization" [x] (instance? goog.Uri x)) -(defn NaN? - "Returns true if num is NaN, else false" - [val] - (js/isNaN val)) - (defn ^:private parsing-err "Construct message for parsing for non-string parsing error" [val] @@ -12296,7 +12306,7 @@ reduces them without incurring seq initialization" -1 (loop [idx (cond (pos? start) start - (neg? start) (max 0 (+ start len)) + (neg? start) (unchecked-max 0 (+ start len)) :else start)] (if (< idx len) (if (= (-nth coll idx) x) @@ -12309,7 +12319,7 @@ reduces them without incurring seq initialization" (if (zero? len) -1 (loop [idx (cond - (pos? start) (min (dec len) start) + (pos? start) (unchecked-min (dec len) start) (neg? start) (+ len start) :else start)] (if (>= idx 0) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 1b7a1235b..f6cfb7a1e 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -1201,17 +1201,21 @@ (core/defmacro ^::ana/numeric neg? [x] `(< ~x 0)) -(core/defmacro ^::ana/numeric max +(core/defmacro ^::ana/numeric unchecked-max ([x] x) - ([x y] `(let [x# ~x, y# ~y] - (~'js* "((~{} > ~{}) ? ~{} : ~{})" x# y# x# y#))) - ([x y & more] `(max (max ~x ~y) ~@more))) + ([x y] + `(let [x# ~x, y# ~y] + (if (> x# y#) x# y#))) + ([x y & more] + `(max (max ~x ~y) ~@more))) -(core/defmacro ^::ana/numeric min +(core/defmacro ^::ana/numeric unchecked-min ([x] x) - ([x y] `(let [x# ~x, y# ~y] - (~'js* "((~{} < ~{}) ? ~{} : ~{})" x# y# x# y#))) - ([x y & more] `(min (min ~x ~y) ~@more))) + ([x y] + `(let [x# ~x, y# ~y] + (if (< x# y#) x# y#))) + ([x y & more] + `(min (min ~x ~y) ~@more))) (core/defmacro ^::ana/numeric js-mod [num div] (core/list 'js* "(~{} % ~{})" num div)) diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index 83f1a65ef..c62b93934 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -1986,3 +1986,10 @@ (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))))))) + +(deftest test-cljs-3425 + (testing "Incorrect min/max handling of ##NaN" + (is (NaN? (min ##NaN 1))) + (is (NaN? (min 1 ##NaN))) + (is (NaN? (max ##NaN 1))) + (is (NaN? (max 1 ##NaN))))) From 1a3d19e05d91ed2a9047a3b521e08488fb5f4ca7 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 16 Nov 2025 19:45:45 -0500 Subject: [PATCH 49/94] fix load-file bug (#283) --- src/main/clojure/cljs/repl.cljc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/cljs/repl.cljc b/src/main/clojure/cljs/repl.cljc index 919bd54ff..c588b4fbf 100644 --- a/src/main/clojure/cljs/repl.cljc +++ b/src/main/clojure/cljs/repl.cljc @@ -609,7 +609,9 @@ env/*compiler*) (cljsc/compile src (assoc opts - :output-file (cljsc/src-file->target-file src) + ;; need to set opts to nil here so that we don't + ;; double up output-dir + :output-file (cljsc/src-file->target-file src nil) :force true :mode :interactive)))] ;; copy over the original source file if source maps enabled From eaf67ed12671104d7f8e784bca2319a352c6f46b Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 16 Nov 2025 20:46:44 -0500 Subject: [PATCH 50/94] put the browser REPL new window behavior behind a flag, default to false (#284) --- src/main/clojure/cljs/repl/browser.clj | 41 +++++++++++++++----------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/main/clojure/cljs/repl/browser.clj b/src/main/clojure/cljs/repl/browser.clj index cf4a34523..2c907c6d1 100644 --- a/src/main/clojure/cljs/repl/browser.clj +++ b/src/main/clojure/cljs/repl/browser.clj @@ -349,21 +349,27 @@ (defn- waiting-to-connect-message [url] (print-str "Waiting for browser to connect to" url "...")) -(defn- maybe-browse-url [base-url] - (try - (browse/browse-url (str base-url "?rel=" (System/currentTimeMillis))) - (catch Throwable t - (if-some [error-message (not-empty (.getMessage t))] - (println "Failed to launch a browser:\n" error-message "\n") - (println "Could not launch a browser.\n")) - (println "You can instead launch a non-browser REPL (Node or Nashorn).\n") - (println "You can disable automatic browser launch with this REPL option") - (println " :launch-browser false") - (println "and you can specify the listen IP address with this REPL option") - (println " :host \"127.0.0.1\"\n") - (println (waiting-to-connect-message base-url))))) - -(defn setup [{:keys [working-dir launch-browser server-state] :as repl-env} {:keys [output-dir] :as opts}] +(defn- maybe-browse-url + ([base-url] + (maybe-browse-url base-url false)) + ([base-url new-window] + (try + (browse/browse-url + (cond-> base-url + new-window (str "?rel=" (System/currentTimeMillis)))) + (catch Throwable t + (if-some [error-message (not-empty (.getMessage t))] + (println "Failed to launch a browser:\n" error-message "\n") + (println "Could not launch a browser.\n")) + (println "You can instead launch a non-browser REPL (Node or Nashorn).\n") + (println "You can disable automatic browser launch with this REPL option") + (println " :launch-browser false") + (println "and you can specify the listen IP address with this REPL option") + (println " :host \"127.0.0.1\"\n") + (println (waiting-to-connect-message base-url))))) +) + +(defn setup [{:keys [working-dir launch-browser new-window server-state] :as repl-env} {:keys [output-dir] :as opts}] (locking lock (when-not (:socket @server-state) (binding [browser-state (:browser-state repl-env) @@ -391,7 +397,7 @@ (server/start repl-env) (let [base-url (str "http://" (:host repl-env) ":" (:port repl-env))] (if launch-browser - (maybe-browse-url base-url) + (maybe-browse-url base-url new-window) (println (waiting-to-connect-message base-url))))))) (.put outs (thread-name) *out*) (swap! server-state update :listeners inc)) @@ -458,8 +464,9 @@ {:host host :port port :launch-browser true + :new-window false :working-dir (->> [".repl" (util/clojurescript-version)] - (remove empty?) (string/join "-")) + (remove empty?) (string/join "-")) :static-dir (cond-> ["." "out/"] output-dir (conj output-dir)) :preloaded-libs [] :src "src/" From 4ae0694acf2422016e1ac2081bff3cb390acf81b Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 17 Nov 2025 20:45:46 -0500 Subject: [PATCH 51/94] MapEntry: switch to case from cond (#285) --- src/main/cljs/cljs/core.cljs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index af6cb080c..12082bdf6 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -6762,14 +6762,16 @@ reduces them without incurring seq initialization" IIndexed (-nth [node n] - (cond (== n 0) key - (== n 1) val - :else (throw (js/Error. "Index out of bounds")))) + (case n + 0 key + 1 val + (throw (js/Error. "Index out of bounds")))) (-nth [node n not-found] - (cond (== n 0) key - (== n 1) val - :else not-found)) + (case n + 0 key + 1 val + not-found)) ILookup (-lookup [node k] (-nth node k nil)) @@ -6779,7 +6781,10 @@ reduces them without incurring seq initialization" (-assoc [node k v] (assoc [key val] k v)) (-contains-key? [node k] - (or (== k 0) (== k 1))) + (case k + 0 true + 1 true + false)) IFind (-find [node k] From 54d013d279a91580b43c5df144e07221355212ac Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 18 Nov 2025 20:39:13 -0500 Subject: [PATCH 52/94] increase string hash cache to 1024 --- src/main/cljs/cljs/core.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 12082bdf6..d1b71a35c 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -1041,7 +1041,7 @@ h)) (defn hash-string [k] - (when (> string-hash-cache-count 255) + (when (> string-hash-cache-count 1024) (set! string-hash-cache (js-obj)) (set! string-hash-cache-count 0)) (if (nil? k) From 19288496e44915d675aa30610cb9fc52427bdc80 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Wed, 19 Nov 2025 11:25:59 -0500 Subject: [PATCH 53/94] cljs.proxy (#286) - add `cljs.proxy` namespace, provides a single function `builder` which can take optional `key-fn`. can proxy maps as Objects, and Vector as array-like. --- src/main/cljs/cljs/proxy.cljs | 137 +++++++++++++++++++++++++++++ src/main/cljs/cljs/proxy/impl.cljs | 15 ++++ 2 files changed, 152 insertions(+) create mode 100644 src/main/cljs/cljs/proxy.cljs create mode 100644 src/main/cljs/cljs/proxy/impl.cljs diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs new file mode 100644 index 000000000..a071868de --- /dev/null +++ b/src/main/cljs/cljs/proxy.cljs @@ -0,0 +1,137 @@ +(ns cljs.proxy + (:refer-global :only [Proxy isNaN]) + (:require [cljs.proxy.impl :refer [SimpleCache]])) + +(defn- write-through [f] + (let [cache (SimpleCache. #js {} 0)] + (fn [x] + (let [v (.get cache x)] + (if (some? v) + v + (.set cache x (f x))))))) + +(def ^{:private true} + desc + #js {:configurable true + :enumerable true}) + +(defn builder + "EXPERIMENTAL: Return a JavaScript Proxy ctor fn with the provided key-fn. You + can proxy ClojureScript map and vectors. Access pattern from JavaScript + will lazily wrap collection values in Proxy if needed. Note key-fn + is only used for proxied ClojureScript maps. This function should map + strings to the appropriate key representation. All maps proxied from the + same ctor fn will share the same key-fn cache." + ([] + (builder keyword)) + ([key-fn] + (js* "var __ctor") + (let [cache-key-fn (write-through key-fn) + vec-handler #js {:get (fn [^cljs.core/IIndexed target prop receiver] + (if (identical? prop "length") + (-count ^cljs.core/ICounted target) + (let [n (js* "+~{}" prop)] + (when (and (number? n) + (not (isNaN n))) + (js/__ctor (-nth target n nil)))))) + + :has (fn [^cljs.core/IAssociative target prop] + (if (identical? prop "length") + true + (let [n (js* "+~{}" prop)] + (and (number? n) + (not (isNaN n)) + (<= 0 n) + (< n (-count ^cljs.core/ICounted target)))))) + + :getPrototypeOf + (fn [target] nil) + + :ownKeys + (fn [target] #js ["length"]) + + :getOwnPropertyDescriptor + (fn [target prop] desc)} + map-handler #js {:get (fn [^cljs.core/ILookup target prop receiver] + (js/__ctor (-lookup target (cache-key-fn prop)))) + + :has (fn [^cljs.core/IAssociative target prop] + (-contains-key? target (cache-key-fn prop))) + + :getPrototypeOf + (fn [target] nil) + + :ownKeys + (fn [target] + (when (nil? (.-cljs$cachedOwnKeys target)) + (set! (. target -cljs$cachedOwnKeys) + (into-array (map -name (keys target))))) + (.-cljs$cachedOwnKeys target)) + + :getOwnPropertyDescriptor + (fn [target prop] desc)} + __ctor (fn [target] + (cond + (implements? IMap target) (Proxy. target map-handler) + (implements? IVector target) (Proxy. target vec-handler) + :else target))] + __ctor))) + +(comment + + (def c (SimpleCache. #js {} 0)) + (.set c "foo" :foo) + (.get c "foo") + (.-cnt c) + (.clear c) + (.get c "foo") + + (def kw (write-through keyword)) + (kw "foo") + + (time + (dotimes [i 1e6] + (kw "foo"))) + + (time + (dotimes [i 1e6] + (keyword "foo"))) + + (def proxy (builder)) + + (def raw-map {:foo 1 :bar 2}) + (def proxied-map (proxy {:foo 1 :bar 2})) + + (require '[goog.object :as gobj]) + (gobj/get proxied-map "foo") + (gobj/get proxied-map "bar") + (gobj/getKeys proxied-map) + (.keys js/Object proxied-map) + + (time + (dotimes [i 1e7] + (unchecked-get proxied-map "foo"))) + + (def k :foo) + (time + (dotimes [i 1e7] + (get raw-map k))) + + (def proxied-vec (proxy [1 2 3 4])) + (alength proxied-vec) + (time + (dotimes [i 1e6] + (alength proxied-vec))) + + (nth [1 2 3 4] 1) + + (aget proxied-vec 1) + + (time + (dotimes [i 1e7] + (aget proxied-vec 1))) + + (def proxied-deep (proxy [{:foo "Hello"}])) + (-> proxied-deep (aget 0) (unchecked-get "foo")) + +) diff --git a/src/main/cljs/cljs/proxy/impl.cljs b/src/main/cljs/cljs/proxy/impl.cljs new file mode 100644 index 000000000..56c99430d --- /dev/null +++ b/src/main/cljs/cljs/proxy/impl.cljs @@ -0,0 +1,15 @@ +(ns cljs.proxy.impl) + +(deftype SimpleCache [^:mutable obj ^:mutable cnt] + Object + (set [this k v] + (when (== cnt 1024) + (.clear this)) + (unchecked-set obj k v) + (set! cnt (inc cnt)) + v) + (get [this k] + (unchecked-get obj k)) + (clear [this] + (set! obj #js {}) + (set! cnt 0))) From 65945bbae1cc7202e8025129f58d2e6f0a35ed29 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 19 Nov 2025 19:10:44 -0500 Subject: [PATCH 54/94] better docstring w/ examples --- src/main/cljs/cljs/proxy.cljs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index a071868de..9a98ae7f2 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -16,12 +16,29 @@ :enumerable true}) (defn builder - "EXPERIMENTAL: Return a JavaScript Proxy ctor fn with the provided key-fn. You - can proxy ClojureScript map and vectors. Access pattern from JavaScript - will lazily wrap collection values in Proxy if needed. Note key-fn - is only used for proxied ClojureScript maps. This function should map - strings to the appropriate key representation. All maps proxied from the - same ctor fn will share the same key-fn cache." + "EXPERIMENTAL: Returns a JavaScript Proxy ctor fn with the provided + key-fn. Invoking the returned fn on ClojureScript maps and vectors + will returned proxied values that can be used transparently as + JavaScript objects and arrays: + + (def proxy (builder)) + + (def proxied-map (proxy {:foo 1 :bar 2})) + (goog.object/get proxied-map \"foo\") ;; => 1 + + (def proxied-vec (proxy [1 2 3 4])) + (aget proxied-vec 1) ;; => 2 + + Access patterns from JavaScript on these proxied values will lazily + recursively return further proxied values: + + (def nested-proxies (proxy [{:foo 1 :bar 2}])) + (-> nested-proxies (aget 0) (goog.object/get \"foo\")) ;; => 1 + + Note key-fn is only used for proxied ClojureScript maps. This + function should map strings to the appropriate key + representation. All maps proxied from the same ctor fn will share + the same key-fn cache." ([] (builder keyword)) ([key-fn] From 3f139e82e9f3fe39ba21079e7908a2999d8a1d40 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 19 Nov 2025 19:11:32 -0500 Subject: [PATCH 55/94] tweak --- src/main/cljs/cljs/proxy.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index 9a98ae7f2..e02e084d8 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -29,7 +29,7 @@ (def proxied-vec (proxy [1 2 3 4])) (aget proxied-vec 1) ;; => 2 - Access patterns from JavaScript on these proxied values will lazily + Access patterns from JavaScript on these proxied values will lazily, recursively return further proxied values: (def nested-proxies (proxy [{:foo 1 :bar 2}])) From 98fced6934fc9018ad7cdd9470c2df02ac50523d Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 19 Nov 2025 19:17:14 -0500 Subject: [PATCH 56/94] clarify defaults --- src/main/cljs/cljs/proxy.cljs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index e02e084d8..b0d40655f 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -37,8 +37,8 @@ Note key-fn is only used for proxied ClojureScript maps. This function should map strings to the appropriate key - representation. All maps proxied from the same ctor fn will share - the same key-fn cache." + representation. If unspecified, key-fn defaults to keyword. All maps + proxied from the same ctor fn will share the same key-fn cache." ([] (builder keyword)) ([key-fn] From 7c718adfce0929fedfd15fcc148d85c85cc1c273 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Thu, 20 Nov 2025 21:17:51 -0500 Subject: [PATCH 57/94] Support Persistent/VECTOR in a backwards compatible way (#287) If we have a symbol of the form A/B and the following conditions are met: 1. A is not an namespace alias 2. A is not a dotted symbol 3. A is an unqualifed var defined or referred in the current ns Then interpret A/B as A.B Note this means that namespace aliases will always shadow unqualified vars defined or referred in the current ns --- src/main/clojure/cljs/analyzer.cljc | 37 ++++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 321489c30..aa52f6fc9 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1313,6 +1313,10 @@ {:name sym} lb)) +(defn qualified->dotted + [sym] + (symbol (str (namespace sym) "." (name sym)))) + (defn resolve-var "Resolve a var. Accepts a side-effecting confirm fn for producing warnings about unresolved vars." @@ -1356,20 +1360,25 @@ (some? lb) (assoc lb :op :local) (some? (namespace sym)) - (let [ns (namespace sym) - ns (if #?(:clj (= "clojure.core" ns) - :cljs (identical? "clojure.core" ns)) - "cljs.core" - ns) - full-ns (resolve-ns-alias env ns - (or (and (js-module-exists? ns) - (gets @env/*compiler* :js-module-index ns :name)) - (symbol ns)))] - (when (some? confirm) - (when (not= current-ns full-ns) - (confirm-ns env full-ns)) - (confirm env full-ns (symbol (name sym)))) - (resolve* env sym full-ns current-ns)) + (let [ns (namespace sym)] + (if-let [resolved (and (nil? (resolve-ns-alias env ns nil)) + (not (dotted-symbol? ns)) + (resolve-var env (symbol ns) nil false) + (resolve-var env (qualified->dotted sym) nil false))] + resolved + (let [ns (if #?(:clj (= "clojure.core" ns) + :cljs (identical? "clojure.core" ns)) + "cljs.core" + ns) + full-ns (resolve-ns-alias env ns + (or (and (js-module-exists? ns) + (gets @env/*compiler* :js-module-index ns :name)) + (symbol ns)))] + (when (some? confirm) + (when (not= current-ns full-ns) + (confirm-ns env full-ns)) + (confirm env full-ns (symbol (name sym)))) + (resolve* env sym full-ns current-ns)))) (dotted-symbol? sym) (let [idx (.indexOf s ".") From 78f35bc1cd6380f2517e4340110e7e3066d3c9f3 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sat, 22 Nov 2025 17:09:00 -0500 Subject: [PATCH 58/94] add :qualified-method :ast, String/.toUpperCase and String/new etc. work (#289) * add :qualified-method :ast, String/.toUpperCase and String/new etc. work * self-hosted special case * fix busted actions, switch to ubuntu and node --- .github/workflows/test.yaml | 90 ++++++++++------------------- src/main/clojure/cljs/analyzer.cljc | 42 +++++++++----- src/main/clojure/cljs/compiler.cljc | 8 +++ 3 files changed, 69 insertions(+), 71 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4420e9d0b..b8c5ae94b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,7 +5,7 @@ jobs: # Runtime Tests runtime-test: name: Runtime Tests - runs-on: macos-14 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -19,50 +19,37 @@ jobs: tools-deps: '1.10.1.763' - name: Cache maven - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-maven with: path: ~/.m2 - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- - name: Cache gitlibs - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-gitlibs with: path: ~/.gitlibs - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- - # - name: Cache JSC - # uses: actions/cache@v4 - # env: - # cache-name: cache-jsc - # with: - # path: WebKit - # key: ${{ runner.os }}-jsc - # restore-keys: | - # ${{ runner.os }}-jsc - - name: Build tests run: clojure -M:runtime.test.build - # - name: Install JSC - # run: ./ci/install_jsc.sh - - name: Run tests run: | - /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/jsc builds/out-adv/core-advanced-test.js | tee test-out.txt + node builds/out-adv/core-advanced-test.js | tee test-out.txt grep -qxF '0 failures, 0 errors.' test-out.txt # Lite Tests lite-test: name: Lite Tests - runs-on: macos-14 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -76,44 +63,31 @@ jobs: tools-deps: '1.10.1.763' - name: Cache maven - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-maven with: path: ~/.m2 - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- - name: Cache gitlibs - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-gitlibs with: path: ~/.gitlibs - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- - # - name: Cache JSC - # uses: actions/cache@v4 - # env: - # cache-name: cache-jsc - # with: - # path: WebKit - # key: ${{ runner.os }}-jsc - # restore-keys: | - # ${{ runner.os }}-jsc - - name: Build tests run: clojure -M:lite.test.build - # - name: Install JSC - # run: ./ci/install_jsc.sh - - name: Run tests run: | - /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/jsc builds/out-lite/lite-test.js | tee test-out.txt + node builds/out-lite/lite-test.js | tee test-out.txt grep -qxF '0 failures, 0 errors.' test-out.txt # Runtime Tests @@ -145,7 +119,7 @@ jobs: # Self-host Tests self-host-test: name: Self-host Tests - runs-on: macos-14 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -159,22 +133,22 @@ jobs: tools-deps: '1.10.1.763' - name: Cache maven - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-maven with: path: ~/.m2 - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- - name: Cache gitlibs - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-gitlibs with: path: ~/.gitlibs - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- @@ -189,7 +163,7 @@ jobs: # Self-parity Tests self-parity-test: name: Self-parity Tests - runs-on: macos-14 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -203,22 +177,22 @@ jobs: tools-deps: '1.10.1.763' - name: Cache maven - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-maven with: path: ~/.m2 - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- - name: Cache gitlibs - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-gitlibs with: path: ~/.gitlibs - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- @@ -233,7 +207,7 @@ jobs: # Compiler Tests compiler-test: name: Compiler Tests - runs-on: macos-14 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -247,22 +221,22 @@ jobs: tools-deps: '1.10.1.763' - name: Cache maven - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-maven with: path: ~/.m2 - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- - name: Cache gitlibs - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-gitlibs with: path: ~/.gitlibs - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- @@ -292,7 +266,7 @@ jobs: # CLI Tests cli-test: name: CLI Tests - runs-on: macos-14 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: @@ -308,22 +282,22 @@ jobs: tools-deps: '1.10.1.763' - name: Cache maven - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-maven with: path: ~/.m2 - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- - name: Cache gitlibs - uses: actions/cache@v4 + uses: actions/cache@v4.2.0 env: cache-name: cache-gitlibs with: path: ~/.gitlibs - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/deps.edn') }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} restore-keys: | ${{ runner.os }}-${{ env.cache-name }}- diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index aa52f6fc9..91ce2f13e 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -4120,6 +4120,7 @@ (select-keys lb [:name :local :arg-id :variadic? :init]))) (let [sym-meta (meta sym) sym-ns (namespace sym) + sym-name (name sym) cur-ns (str (-> env :ns :name)) ;; when compiling a macros namespace that requires itself, we need ;; to resolve calls to `my-ns.core/foo` to `my-ns.core$macros/foo` @@ -4130,21 +4131,36 @@ (not (gstring/endsWith sym-ns "$macros")) (= sym-ns (subs cur-ns 0 (- (count cur-ns) 7)))) (symbol (str sym-ns "$macros") (name sym)) - sym)]) - info (if-not (contains? sym-meta ::analyzed) + sym)])] + (if (and sym-ns + (nil? (resolve-ns-alias env sym-ns nil)) + (not= ".." sym-name) ;; special case `..` macro in self-hosted + (or (= "new" sym-name) + (string/starts-with? sym-name "."))) + (merge + {:op :qualified-method + :env env + :form sym + :class (symbol sym-ns)} + (if (= "new" sym-name) + {:kind :new + :name (symbol sym-name)} + {:kind :method + :name (symbol (subs sym-name 1))})) + (let [info (if-not (contains? sym-meta ::analyzed) (resolve-existing-var env sym) (resolve-var env sym))] - (assert (:op info) (:op info)) - (desugar-dotted-expr - (if-not (true? (:def-var env)) - (merge - (assoc ret :info info) - (select-keys info [:op :name :ns :tag]) - (when-let [const-expr (:const-expr info)] - {:const-expr const-expr})) - (let [info (resolve-var env sym)] - (merge (assoc ret :op :var :info info) - (select-keys info [:op :name :ns :tag])))))))))) + (assert (:op info) (:op info)) + (desugar-dotted-expr + (if-not (true? (:def-var env)) + (merge + (assoc ret :info info) + (select-keys info [:op :name :ns :tag]) + (when-let [const-expr (:const-expr info)] + {:const-expr const-expr})) + (let [info (resolve-var env sym)] + (merge (assoc ret :op :var :info info) + (select-keys info [:op :name :ns :tag])))))))))))) (defn excluded? #?(:cljs {:tag boolean}) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index 8a17d17d2..a791cc2ba 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -1301,6 +1301,14 @@ (comma-sep args) "))"))) +(defmethod emit* :qualified-method + [{ctor :class :keys [args env kind name]}] + (if (= :new kind) + (emit-wrap env + (emits "(function (...args) { return Reflect.construct(" ctor ", args) })")) + (emit-wrap env + (emits "(function (x, ...args) { return Reflect.apply(" ctor ".prototype." name ", x, args) })")))) + (defmethod emit* :set! [{:keys [target val env]}] (emit-wrap env (emits "(" target " = " val ")"))) From 6c3f12733a4081acb8afddc02e0106a5387fafe4 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 23 Nov 2025 08:32:50 -0500 Subject: [PATCH 59/94] Add some simple method value tests (#290) --- src/test/cljs/cljs/core_test.cljs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index c62b93934..3cd24205a 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -7,6 +7,7 @@ ; You must not remove this notice, or any other, from this software. (ns cljs.core-test + (:refer-global :only [Object String]) (:refer-clojure :exclude [iter]) (:require [cljs.test :refer-macros [deftest testing is are]] [clojure.test.check :as tc] @@ -1993,3 +1994,16 @@ (is (NaN? (min 1 ##NaN))) (is (NaN? (max ##NaN 1))) (is (NaN? (max 1 ##NaN))))) + +(deftest test-static-props-methods + (is (= [] PersistentVector/EMPTY)) + (let [f String/fromCharCode] + (is (= "A" (f 65))))) + +(deftest test-new-method + (let [f Object/new] + (some? (f)))) + +(deftest test-instance-method-new + (is (= ["FOO" "BAR" "BAZ"] + (map String/.toUpperCase ["foo" "bar" "baz"])))) From 0bb9e9c6fb4761a1fceab882fb5e04e3565b7819 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 23 Nov 2025 08:47:21 -0500 Subject: [PATCH 60/94] bump Closure Compiler to v20250820 (#291) - bump version - remove diagnostic that no longer exists - remove DependencyOptions logic that does not work anymore --- deps.edn | 2 +- pom.template.xml | 2 +- project.clj | 2 +- script/bootstrap | 2 +- src/main/clojure/cljs/closure.clj | 9 +-------- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/deps.edn b/deps.edn index 5233627bc..7651fd0c1 100644 --- a/deps.edn +++ b/deps.edn @@ -1,6 +1,6 @@ {:paths ["src/main/clojure" "src/main/cljs" "resources"] :deps - {com.google.javascript/closure-compiler {:mvn/version "v20250402"} + {com.google.javascript/closure-compiler {:mvn/version "v20250820"} com.cognitect/transit-java {:mvn/version "1.0.362"} org.clojure/clojure {:mvn/version "1.10.0"} org.clojure/core.specs.alpha {:mvn/version "0.1.24"} diff --git a/pom.template.xml b/pom.template.xml index 884a2d628..04b86a6c6 100644 --- a/pom.template.xml +++ b/pom.template.xml @@ -30,7 +30,7 @@ com.google.javascript closure-compiler - v20250402 + v20250820 org.clojure diff --git a/project.clj b/project.clj index 3977529e5..0bba1a489 100644 --- a/project.clj +++ b/project.clj @@ -15,7 +15,7 @@ [org.clojure/test.check "1.1.1" :scope "test"] [com.cognitect/transit-java "1.0.362"] [org.clojure/google-closure-library "0.0-20250515-f04e4c0e"] - [com.google.javascript/closure-compiler "v20250402"]] + [com.google.javascript/closure-compiler "v20250820"]] :profiles {:1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]} :uberjar {:aot :all :main cljs.main} :closure-snapshot {:dependencies [[com.google.javascript/closure-compiler-unshaded "1.0-SNAPSHOT"]]}} diff --git a/script/bootstrap b/script/bootstrap index 6b2a6e44b..464cc08da 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -5,7 +5,7 @@ set -e CLOJURE_RELEASE="1.9.0" SPEC_ALPHA_RELEASE="0.1.143" CORE_SPECS_ALPHA_RELEASE="0.1.24" -CLOSURE_RELEASE="20250402" +CLOSURE_RELEASE="20250820" GCLOSURE_LIB_RELEASE="0.0-20250515-f04e4c0e" TREADER_RELEASE="1.3.6" TEST_CHECK_RELEASE="1.1.1" diff --git a/src/main/clojure/cljs/closure.clj b/src/main/clojure/cljs/closure.clj index 05fa761aa..f91c52d77 100644 --- a/src/main/clojure/cljs/closure.clj +++ b/src/main/clojure/cljs/closure.clj @@ -185,7 +185,6 @@ :report-unknown-types DiagnosticGroups/REPORT_UNKNOWN_TYPES :strict-missing-properties DiagnosticGroups/STRICT_MISSING_PROPERTIES :strict-module-dep-check DiagnosticGroups/STRICT_MODULE_DEP_CHECK - :strict-requires DiagnosticGroups/STRICT_REQUIRES :suspicious-code DiagnosticGroups/SUSPICIOUS_CODE :too-many-type-params DiagnosticGroups/TOO_MANY_TYPE_PARAMS :tweaks DiagnosticGroups/TWEAKS @@ -1970,13 +1969,7 @@ (.toSource closure-compiler ast-root))))) (defn- sorting-dependency-options [] - (try - (if (contains? (:flags (clojure.reflect/reflect DependencyOptions)) :abstract) - (eval '(do - (import '(com.google.javascript.jscomp DependencyOptions)) - (DependencyOptions/sortOnly))) - (doto (DependencyOptions.) - (.setDependencySorting true))))) + (DependencyOptions/sortOnly)) (defn convert-js-modules "Takes a list JavaScript modules as an IJavaScript and rewrites them into a Google From 90e7fba4360435dae7c44d0e3d9437115f9ceacc Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 23 Nov 2025 08:49:06 -0500 Subject: [PATCH 61/94] update changes (#292) --- changes.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/changes.md b/changes.md index 4e616fb81..0f970948a 100644 --- a/changes.md +++ b/changes.md @@ -1,3 +1,26 @@ +## 1.12. + +### Changes +* CLJS-3233: `:refer-global` + `:only`, `:require-global` +* CLJS-3451: make munge-str public +* various small DCE enhancements +* browser REPL reuses same window + +### Enhancements +* Clojure method values syntax support +* `cljs.proxy`, experimental namespace for efficient interop +* CLJS-2471: ChunkedSeq should implemented ICounted +* CLJS-3452: optimize str by compiling to + / .toString + compile time optimizations +* `:lite-mode` and `:elide-to-string`, new experimental compiler flags for smaller artifacts +* CLJS-3439: REPL doc support for externs + +### Fixes +* Fix REPL load-file issue +* CLJS-3425: Incorrect handling of ##NaN with min/max +* CLJS-3461: don't hard-code destructuring to PAM +* CLJS-3454: New set instances are created when redundant data is added +* CLJS-3438: Inference for `goog.object/containsKey` returns any, not boolean + ## 1.12.42 ### Changes From 81fde7c3b9893b5f12fb91463839f947f46a1385 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 23 Nov 2025 10:40:43 -0500 Subject: [PATCH 62/94] fixed qualified-method resolution bug --- src/main/clojure/cljs/analyzer.cljc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 91ce2f13e..f1c337420 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -4141,12 +4141,12 @@ {:op :qualified-method :env env :form sym - :class (symbol sym-ns)} - (if (= "new" sym-name) - {:kind :new - :name (symbol sym-name)} - {:kind :method - :name (symbol (subs sym-name 1))})) + :class (analyze-symbol env (symbol sym-ns))} + (if (= "new" sym-name) + {:kind :new + :name (symbol sym-name)} + {:kind :method + :name (symbol (subs sym-name 1))})) (let [info (if-not (contains? sym-meta ::analyzed) (resolve-existing-var env sym) (resolve-var env sym))] From 0b2fc9618234c0baa4f996e0d0d80db98af34333 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 23 Nov 2025 13:20:04 -0500 Subject: [PATCH 63/94] 1.12.110 --- README.md | 6 +++--- changes.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98323b88b..b574f7151 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ Official web site: https://clojurescript.org ## Releases and dependency information ## -Latest stable release: 1.12.42 +Latest stable release: 1.12.110 * [All released versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22clojurescript%22) [Clojure deps.edn](http://clojure.org/guides/deps_and_cli) dependency information: ``` - org.clojure/clojurescript {:mvn/version "1.12.42"} + org.clojure/clojurescript {:mvn/version "1.12.110"} ``` [Leiningen](https://github.com/technomancy/leiningen/) dependency information: ``` -[org.clojure/clojurescript "1.12.42"] +[org.clojure/clojurescript "1.12.110"] ``` [Maven](https://maven.apache.org) dependency information: diff --git a/changes.md b/changes.md index 0f970948a..6ae85724b 100644 --- a/changes.md +++ b/changes.md @@ -1,4 +1,4 @@ -## 1.12. +## 1.12.110 ### Changes * CLJS-3233: `:refer-global` + `:only`, `:require-global` From 7f17d12309810fd0121e4076cff16a1c4ef6f397 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 23 Nov 2025 23:27:00 -0500 Subject: [PATCH 64/94] fix REPL issue w/ :refer-global usage in namespaces found by trying to use cljs.proxy --- src/main/clojure/cljs/closure.clj | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/cljs/closure.clj b/src/main/clojure/cljs/closure.clj index f91c52d77..55e7181f0 100644 --- a/src/main/clojure/cljs/closure.clj +++ b/src/main/clojure/cljs/closure.clj @@ -1609,10 +1609,14 @@ ;; even under Node.js where runtime require is possible ;; this is necessary - see CLJS-2151 (ns-list (cond->> + ;; remove the global js namespace, it's not real + ;; comes from :refer-global ;; remove external? foreign deps - they are already loaded - ;; in the environment, there is nothing to do. - ;; :require-global is the typical case here - (remove ana/external-dep? (deps/-requires input)) + ;; in the environment, there is nothing to do. + ;; :require-global is the typical case here + (->> (deps/-requires input) ;; returns nses as strings, not symbols + (remove #{"js"}) + (remove ana/external-dep?)) ;; under Node.js we emit native `require`s for these (= :nodejs (:target opts)) (filter (complement ana/node-module-dep?)))) From 2c220503fa2c105a89b0fae968aa108965007eee Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 23 Nov 2025 23:29:04 -0500 Subject: [PATCH 65/94] 1.12.112 --- README.md | 6 +++--- changes.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b574f7151..80f7d1f5f 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ Official web site: https://clojurescript.org ## Releases and dependency information ## -Latest stable release: 1.12.110 +Latest stable release: 1.12.112 * [All released versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22clojurescript%22) [Clojure deps.edn](http://clojure.org/guides/deps_and_cli) dependency information: ``` - org.clojure/clojurescript {:mvn/version "1.12.110"} + org.clojure/clojurescript {:mvn/version "1.12.112"} ``` [Leiningen](https://github.com/technomancy/leiningen/) dependency information: ``` -[org.clojure/clojurescript "1.12.110"] +[org.clojure/clojurescript "1.12.112"] ``` [Maven](https://maven.apache.org) dependency information: diff --git a/changes.md b/changes.md index 6ae85724b..1f185fa48 100644 --- a/changes.md +++ b/changes.md @@ -1,4 +1,4 @@ -## 1.12.110 +## 1.12.112 ### Changes * CLJS-3233: `:refer-global` + `:only`, `:require-global` From f2c8575ff5de5016247f14fadecc2d841692c64f Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 24 Nov 2025 00:55:06 -0500 Subject: [PATCH 66/94] fix maven example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80f7d1f5f..92b1ab55d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Latest stable release: 1.12.112 org.clojure clojurescript - 1.12.38 + 1.12.112 ``` From c57b06666a0850728ec1ff9a25e452467f5fbae2 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Mon, 24 Nov 2025 10:22:48 -0500 Subject: [PATCH 67/94] bump central publishing plugin --- pom.template.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.template.xml b/pom.template.xml index 04b86a6c6..f6a75f483 100644 --- a/pom.template.xml +++ b/pom.template.xml @@ -409,7 +409,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.7.0 + 0.9.0 true central From b52c788e806136fbe56232be83f8e6ed865741fd Mon Sep 17 00:00:00 2001 From: JarrodCTaylor Date: Mon, 24 Nov 2025 10:36:24 -0600 Subject: [PATCH 68/94] Change server-id from sonatype to central Use the new central publishing system --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 766c05f63..31686eb49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: java-version: 21 distribution: 'temurin' cache: 'maven' - server-id: sonatype-nexus-staging + server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }} From deff2db82be4a7ee38f3f1b708e162c253c1682f Mon Sep 17 00:00:00 2001 From: JarrodCTaylor Date: Mon, 24 Nov 2025 11:13:54 -0600 Subject: [PATCH 69/94] Use central creds for release workflow --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31686eb49..8a2d87838 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,12 +30,12 @@ jobs: server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - gpg-private-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }} + gpg-private-key: ${{ secrets.CENTRAL_GPG_SECRET_KEY }} gpg-passphrase: GPG_PASSPHRASE - name: Release run: script/build env: HUDSON: ${{ github.event.inputs.deploy }} - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.CENTRAL_GPG_SECRET_KEY_PASSWORD }} From cdf7d4572e67d54936483578541693535526710f Mon Sep 17 00:00:00 2001 From: davidnolen Date: Mon, 24 Nov 2025 12:29:23 -0500 Subject: [PATCH 70/94] update for actual released version --- README.md | 8 ++++---- changes.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 92b1ab55d..3fc82e6e0 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ Official web site: https://clojurescript.org ## Releases and dependency information ## -Latest stable release: 1.12.112 +Latest stable release: 1.12.116 * [All released versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22clojurescript%22) [Clojure deps.edn](http://clojure.org/guides/deps_and_cli) dependency information: ``` - org.clojure/clojurescript {:mvn/version "1.12.112"} + org.clojure/clojurescript {:mvn/version "1.12.116"} ``` [Leiningen](https://github.com/technomancy/leiningen/) dependency information: ``` -[org.clojure/clojurescript "1.12.112"] +[org.clojure/clojurescript "1.12.116"] ``` [Maven](https://maven.apache.org) dependency information: @@ -28,7 +28,7 @@ Latest stable release: 1.12.112 org.clojure clojurescript - 1.12.112 + 1.12.116 ``` diff --git a/changes.md b/changes.md index 1f185fa48..55c6119e2 100644 --- a/changes.md +++ b/changes.md @@ -1,4 +1,4 @@ -## 1.12.112 +## 1.12.116 ### Changes * CLJS-3233: `:refer-global` + `:only`, `:require-global` From 8757eaabe952d329db83940d31718ccab019886e Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 24 Nov 2025 20:42:40 -0500 Subject: [PATCH 71/94] CLJS-3463: rename all the lite mode data structures / fns to avoid clashing (#293) --- src/main/cljs/cljs/analyzer/passes/lite.cljc | 12 +-- src/main/cljs/cljs/core.cljs | 90 +++++++++---------- src/main/cljs/cljs/spec/alpha.cljs | 2 +- src/main/clojure/cljs/compiler.cljc | 12 +-- src/test/cljs/cljs/collections_test.cljs | 30 +++---- src/test/cljs/cljs/lite_collections_test.cljs | 12 +-- src/test/clojure/cljs/analyzer_pass_tests.clj | 4 +- 7 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/main/cljs/cljs/analyzer/passes/lite.cljc b/src/main/cljs/cljs/analyzer/passes/lite.cljc index 08a7e03de..d0ea8c659 100644 --- a/src/main/cljs/cljs/analyzer/passes/lite.cljc +++ b/src/main/cljs/cljs/analyzer/passes/lite.cljc @@ -12,21 +12,21 @@ (defn var? [ast] (= :var (:op ast))) -(def ctor->simple-ctor - '{cljs.core/vector cljs.core/simple-vector - cljs.core/vec cljs.core/simple-vec}) +(def ctor->ctor-lite + '{cljs.core/vector cljs.core/vector-lite + cljs.core/vec cljs.core/vec-lite}) (defn update-var [{:keys [name] :as ast}] - (let [replacement (get ctor->simple-ctor name)] + (let [replacement (get ctor->ctor-lite name)] (-> ast (assoc :name replacement) (assoc-in [:info :name] replacement)))) (defn replace-var? [ast] (and (var? ast) - (contains? ctor->simple-ctor (:name ast)))) + (contains? ctor->ctor-lite (:name ast)))) (defn use-lite-types [env ast _] (cond-> ast - (replace-var? ast) update-var)) \ No newline at end of file + (replace-var? ast) update-var)) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index d1b71a35c..ad87e9505 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -10432,7 +10432,7 @@ reduces them without incurring seq initialization" (implements? IMeta obj) (not (nil? (meta obj))))) -(declare Vector) +(declare VectorLite) (defn- pr-writer-impl [obj writer opts] @@ -12287,9 +12287,9 @@ reduces them without incurring seq initialization" ;; ----------------------------------------------------------------------------- ;; Original 2011 Copy-on-Write Types -;;; Vector +;;; VectorLite -(deftype VectorIterator [arr ^:mutable i] +(deftype VectorLiteIterator [arr ^:mutable i] Object (hasNext [_] (< i (alength arr))) @@ -12298,7 +12298,7 @@ reduces them without incurring seq initialization" (set! i (inc i)) x))) -(deftype Vector [meta array ^:mutable __hash] +(deftype VectorLite [meta array ^:mutable __hash] Object (toString [coll] (pr-str* coll)) @@ -12337,10 +12337,10 @@ reduces them without incurring seq initialization" (-with-meta [coll new-meta] (if (identical? new-meta meta) coll - (Vector. new-meta array __hash))) + (VectorLite. new-meta array __hash))) ICloneable - (-clone [coll] (Vector. meta array __hash)) + (-clone [coll] (VectorLite. meta array __hash)) IMeta (-meta [coll] meta) @@ -12354,17 +12354,17 @@ reduces them without incurring seq initialization" (if (> (alength array) 0) (let [new-array (aclone array)] (. new-array (pop)) - (Vector. meta new-array nil)) + (VectorLite. meta new-array nil)) (throw (js/Error. "Can't pop empty vector")))) ICollection (-conj [coll o] (let [new-array (aclone array)] (.push new-array o) - (Vector. meta new-array nil))) + (VectorLite. meta new-array nil))) IEmptyableCollection - (-empty [coll] (with-meta (. Vector -EMPTY) meta)) + (-empty [coll] (with-meta (. VectorLite -EMPTY) meta)) ISequential IEquiv @@ -12405,7 +12405,7 @@ reduces them without incurring seq initialization" (if (number? k) (let [new-array (aclone array)] (aset new-array k v) - (Vector. meta new-array nil)) + (VectorLite. meta new-array nil)) (throw (js/Error. "Vector's key for assoc must be a number.")))) (-contains-key? [coll k] (if (integer? k) @@ -12482,24 +12482,24 @@ reduces them without incurring seq initialization" IIterable (-iterator [coll] - (VectorIterator. array 0)) + (VectorLiteIterator. array 0)) IPrintWithWriter (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll))) -(es6-iterable Vector) +(es6-iterable VectorLite) -(set! (. Vector -EMPTY) (Vector. nil (array) nil)) +(set! (. VectorLite -EMPTY) (VectorLite. nil (array) nil)) -(set! (. Vector -fromArray) (fn [xs] (Vector. nil xs nil))) +(set! (. VectorLite -fromArray) (fn [xs] (VectorLite. nil xs nil))) -(defn simple-vector +(defn vector-lite [& args] (if (and (instance? IndexedSeq args) (zero? (.-i args))) - (.fromArray Vector (aclone (.-arr args))) - (Vector. nil (into-array args) nil))) + (.fromArray VectorLite (aclone (.-arr args))) + (VectorLite. nil (into-array args) nil))) -(defn simple-vec +(defn vec-lite [coll] (cond (map-entry? coll) @@ -12509,7 +12509,7 @@ reduces them without incurring seq initialization" (with-meta coll nil) (array? coll) - (.fromArray Vector coll) + (.fromArray VectorLite coll) :else (into [] coll))) @@ -12538,7 +12538,7 @@ reduces them without incurring seq initialization" (recur (inc i))))) new-obj)) -(declare simple-hash-map HashMap) +(declare hash-map-lite HashMapLite) (defn- keyword->obj-map-key [k] @@ -12656,7 +12656,7 @@ reduces them without incurring seq initialization" (-kv-reduce coll (fn [ret k v] (-assoc ret k v)) - (simple-hash-map k v)) + (hash-map-lite k v)) meta)))) (-contains-key? [coll k] (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] @@ -12783,7 +12783,7 @@ reduces them without incurring seq initialization" ; hashobj. Each values in hashobj is actually a bucket in order to handle hash ; collisions. A bucket is an array of alternating keys (not their hashes) and ; vals. -(deftype HashMap [meta count hashobj ^:mutable __hash] +(deftype HashMapLite [meta count hashobj ^:mutable __hash] Object (toString [coll] (pr-str* coll)) @@ -12806,13 +12806,13 @@ reduces them without incurring seq initialization" #(f (-val %) (-key %)))))) IWithMeta - (-with-meta [coll meta] (HashMap. meta count hashobj __hash)) + (-with-meta [coll meta] (HashMapLite. meta count hashobj __hash)) IMeta (-meta [coll] meta) ICloneable - (-clone [coll] (HashMap. meta count hashobj __hash)) + (-clone [coll] (HashMapLite. meta count hashobj __hash)) ICollection (-conj [coll entry] @@ -12821,7 +12821,7 @@ reduces them without incurring seq initialization" (reduce -conj coll entry))) IEmptyableCollection - (-empty [coll] (with-meta (. HashMap -EMPTY) meta)) + (-empty [coll] (with-meta (. HashMapLite -EMPTY) meta)) IEquiv (-equiv [coll other] (equiv-map coll other)) @@ -12874,15 +12874,15 @@ reduces them without incurring seq initialization" (do ; found key, replace (aset new-bucket (inc i) v) - (HashMap. meta count new-hashobj nil))) + (HashMapLite. meta count new-hashobj nil))) (do ; did not find key, append (.push new-bucket k v) - (HashMap. meta (inc count) new-hashobj nil)))) + (HashMapLite. meta (inc count) new-hashobj nil)))) (let [new-hashobj (gobject/clone hashobj)] ; did not find bucket (unchecked-set new-hashobj h (array k v)) - (HashMap. meta (inc count) new-hashobj nil))))) + (HashMapLite. meta (inc count) new-hashobj nil))))) (-contains-key? [coll k] (let [bucket (unchecked-get hashobj (hash k)) i (when bucket (scan-array-equiv 2 k bucket))] @@ -12902,7 +12902,7 @@ reduces them without incurring seq initialization" (let [new-bucket (aclone bucket)] (.splice new-bucket i 2) (unchecked-set new-hashobj h new-bucket))) - (HashMap. meta (dec count) new-hashobj nil)) + (HashMapLite. meta (dec count) new-hashobj nil)) ; key not found, return coll unchanged coll))) @@ -12961,27 +12961,27 @@ reduces them without incurring seq initialization" (-pr-writer [coll writer opts] (print-map coll pr-writer writer opts))) -(es6-iterable HashMap) +(es6-iterable HashMapLite) -(set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj) empty-unordered-hash)) +(set! (. HashMapLite -EMPTY) (HashMapLite. nil 0 (js-obj) empty-unordered-hash)) -(set! (. HashMap -fromArrays) (fn [ks vs] +(set! (. HashMapLite -fromArrays) (fn [ks vs] (let [len (.-length ks)] - (loop [i 0, out (. HashMap -EMPTY)] + (loop [i 0, out (. HashMapLite -EMPTY)] (if (< i len) (recur (inc i) (assoc out (aget ks i) (aget vs i))) out))))) -(defn simple-hash-map +(defn hash-map-lite "keyval => key val Returns a new hash map with supplied mappings." [& keyvals] - (loop [in (seq keyvals), out (. HashMap -EMPTY)] + (loop [in (seq keyvals), out (. HashMapLite -EMPTY)] (if in (recur (nnext in) (-assoc out (first in) (second in))) out))) -(deftype Set [meta hash-map ^:mutable __hash] +(deftype SetLite [meta hash-map ^:mutable __hash] Object (toString [coll] (pr-str* coll)) @@ -13003,23 +13003,23 @@ reduces them without incurring seq initialization" (-with-meta [coll new-meta] (if (identical? new-meta meta) coll - (Set. new-meta hash-map __hash))) + (SetLite. new-meta hash-map __hash))) IMeta (-meta [coll] meta) ICloneable - (-clone [coll] (Set. meta hash-map __hash)) + (-clone [coll] (SetLite. meta hash-map __hash)) ICollection (-conj [coll o] (let [new-hash-map (assoc hash-map o o)] (if (identical? new-hash-map hash-map) coll - (Set. meta new-hash-map nil)))) + (SetLite. meta new-hash-map nil)))) IEmptyableCollection - (-empty [coll] (with-meta (. Set -EMPTY) meta)) + (-empty [coll] (with-meta (. SetLite -EMPTY) meta)) IEquiv (-equiv [coll other] @@ -13058,7 +13058,7 @@ reduces them without incurring seq initialization" (let [new-hash-map (-dissoc hash-map v)] (if (identical? new-hash-map hash-map) coll - (Set. meta new-hash-map nil)))) + (SetLite. meta new-hash-map nil)))) IEditableCollection (-as-transient [coll] @@ -13090,18 +13090,18 @@ reduces them without incurring seq initialization" IPrintWithWriter (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll))) -(es6-iterable Set) +(es6-iterable SetLite) -(set! (. Set -EMPTY) (Set. nil (. HashMap -EMPTY) empty-unordered-hash)) +(set! (. SetLite -EMPTY) (SetLite. nil (. HashMapLite -EMPTY) empty-unordered-hash)) -(defn simple-set +(defn set-lite [coll] (if (set? coll) (-with-meta coll nil) (let [in (seq coll)] (if (nil? in) #{} - (loop [in in out (. Set -EMPTY)] + (loop [in in out (. SetLite -EMPTY)] (if-not (nil? in) (recur (next in) (-conj out (first in))) out)))))) diff --git a/src/main/cljs/cljs/spec/alpha.cljs b/src/main/cljs/cljs/spec/alpha.cljs index b348d9928..740262aad 100644 --- a/src/main/cljs/cljs/spec/alpha.cljs +++ b/src/main/cljs/cljs/spec/alpha.cljs @@ -148,7 +148,7 @@ (specize* ([s] (spec-impl s s nil nil)) ([s form] (spec-impl form s nil nil))) - Set + SetLite (specize* ([s] (spec-impl s s nil nil)) ([s form] (spec-impl form s nil nil))) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index a791cc2ba..6e9d152ff 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -540,8 +540,8 @@ (defn emit-lite-map [keys vals comma-sep distinct-keys?] (if (zero? (count keys)) - (emits "cljs.core.HashMap.EMPTY") - (emits "cljs.core.HashMap.fromArrays([" (comma-sep keys) "], [" (comma-sep vals) "])"))) + (emits "cljs.core.HashMapLite.EMPTY") + (emits "cljs.core.HashMapLite.fromArrays([" (comma-sep keys) "], [" (comma-sep vals) "])"))) (defn emit-map [keys vals comma-sep distinct-keys?] (cond @@ -590,8 +590,8 @@ (defn emit-lite-vector [items comma-sep] (if (empty? items) - (emits "cljs.core.Vector.EMPTY") - (emits "new cljs.core.Vector(null, [" (comma-sep items) "], null)"))) + (emits "cljs.core.VectorLite.EMPTY") + (emits "new cljs.core.VectorLite(null, [" (comma-sep items) "], null)"))) (defmethod emit* :vector [{:keys [items env]}] @@ -618,8 +618,8 @@ (defn emit-lite-set [items comma-sep distinct-constants?] (if (empty? items) - (emits "cljs.core.Set.EMPTY") - (emits "cljs.core.simple_set([" (comma-sep items) "])"))) + (emits "cljs.core.SetLite.EMPTY") + (emits "cljs.core.set_lite([" (comma-sep items) "])"))) (defmethod emit* :set [{:keys [items env]}] diff --git a/src/test/cljs/cljs/collections_test.cljs b/src/test/cljs/cljs/collections_test.cljs index 20aae48f0..8607d30c2 100644 --- a/src/test/cljs/cljs/collections_test.cljs +++ b/src/test/cljs/cljs/collections_test.cljs @@ -1184,36 +1184,36 @@ (is (= (transient (obj-map :a 1 :b 2)) (obj-map :a 1 :b 2)))) -(deftest test-simple-hash-map - (let [a (simple-hash-map)] +(deftest test-hash-map-lite + (let [a (hash-map-lite)] (is (empty? a)) (is (zero? (count a)))) - (let [b (simple-hash-map :a 1)] + (let [b (hash-map-lite :a 1)] (is (not (empty? b))) (is (== 1 (count b)))) - (let [c (simple-hash-map :a 1 :b 2 :c 3)] + (let [c (hash-map-lite :a 1 :b 2 :c 3)] (is (== 3 (count c))) (is (= 1 (get c :a))) (is (= 1 (:a c))) (is (every? keyword? (keys c))) (is (= (set [:a :b :c]) (set (keys c))))) - (is (= (simple-hash-map :a 1 :b 2 :c 3) - (simple-hash-map :a 1 :b 2 :c 3))) - (is (= (simple-hash-map :a 1 :b 2) - (into (simple-hash-map) [[:a 1] [:b 2]]))) + (is (= (hash-map-lite :a 1 :b 2 :c 3) + (hash-map-lite :a 1 :b 2 :c 3))) + (is (= (hash-map-lite :a 1 :b 2) + (into (hash-map-lite) [[:a 1] [:b 2]]))) (is (= (merge-with + - (simple-hash-map :a 1 :b 2) - (simple-hash-map :a 1 :b 2)) - (into (simple-hash-map) [[:a 2] [:b 4]]))) - (is (= (transient (simple-hash-map :a 1 :b 2)) - (simple-hash-map :a 1 :b 2)))) + (hash-map-lite :a 1 :b 2) + (hash-map-lite :a 1 :b 2)) + (into (hash-map-lite) [[:a 2] [:b 4]]))) + (is (= (transient (hash-map-lite :a 1 :b 2)) + (hash-map-lite :a 1 :b 2)))) -(deftest test-simple-set +(deftest test-set-lite (is (= #{1 2 3} #{1 2 3})) (is (= 3 (count #{1 2 3}))) (let [x #{1 2 3}] (is (every? #(contains? x %) [1 2 3]))) - (is (= (simple-set [[3 4] [1 2] [5 6]]) + (is (= (set-lite [[3 4] [1 2] [5 6]]) (into #{} [[3 4] [1 2] [5 6]])))) (comment diff --git a/src/test/cljs/cljs/lite_collections_test.cljs b/src/test/cljs/cljs/lite_collections_test.cljs index a96148f00..99cb2be29 100644 --- a/src/test/cljs/cljs/lite_collections_test.cljs +++ b/src/test/cljs/cljs/lite_collections_test.cljs @@ -18,10 +18,10 @@ (let [a {:foo 1}] (is (== 1 (:foo a))))) -(deftest test-simple-set-with-set - (is (= (simple-set []) (set []))) - (is (= (set []) (simple-set []))) - (is (= (simple-set [(MapEntry. 1 2 nil)]) +(deftest test-set-lite-with-set + (is (= (set-lite []) (set []))) + (is (= (set []) (set-lite []))) + (is (= (set-lite [(MapEntry. 1 2 nil)]) (set [(MapEntry. 1 2 nil)])))) (deftest test-obj-map-clj->js @@ -31,9 +31,9 @@ (deftest test-unchanged-identical? (let [m (obj-map :foo 1)] (identical? m (assoc m :foo 1))) - (let [m (simple-hash-map :foo 1)] + (let [m (hash-map-lite :foo 1)] (identical? m (assoc m :foo 1))) - (let [s (simple-set [:foo])] + (let [s (set-lite [:foo])] (identical? s (conj s :foo)))) (comment diff --git a/src/test/clojure/cljs/analyzer_pass_tests.clj b/src/test/clojure/cljs/analyzer_pass_tests.clj index 1a451d491..643352b7a 100644 --- a/src/test/clojure/cljs/analyzer_pass_tests.clj +++ b/src/test/clojure/cljs/analyzer_pass_tests.clj @@ -185,14 +185,14 @@ (comp/with-core-cljs {} (fn [] (analyze aenv 'cljs.core/vec))))] - (is (= 'cljs.core/simple-vec + (is (= 'cljs.core/vec-lite (-> ast :name) (-> ast :info :name)))) (let [ast (env/with-compiler-env env (comp/with-core-cljs {} (fn [] (analyze aenv 'cljs.core/vector))))] - (is (= 'cljs.core/simple-vector + (is (= 'cljs.core/vector-lite (-> ast :name) (-> ast :info :name)))))) From c4bc714ae4853ae91e55f342e89df0809aaed310 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Tue, 25 Nov 2025 06:10:21 -0500 Subject: [PATCH 72/94] be less specific around int coercion fns (#294) cljs.tools.reader uses `int` to coerce numeric unicode chars, with better inference of JS APIs this triggered a numeric warning. But this warning is a bit bogus in that `int` is for coercion anyway. Clojure is vague about `int` behavior, we should be equally vague. --- src/main/cljs/cljs/core.cljs | 8 ++++---- src/main/clojure/cljs/core.cljc | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index ad87e9505..40944f0c8 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -2932,22 +2932,22 @@ reduces them without incurring seq initialization" (Math/ceil q))) (defn int - "Coerce to int by stripping decimal places." + "Coerce to int." [x] (bit-or x 0)) (defn unchecked-int - "Coerce to int by stripping decimal places." + "Coerce to int." [x] (fix x)) (defn long - "Coerce to long by stripping decimal places. Identical to `int'." + "Coerce to long. Identical to `int'." [x] (fix x)) (defn unchecked-long - "Coerce to long by stripping decimal places. Identical to `int'." + "Coerce to long. Identical to `int'." [x] (fix x)) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index f6cfb7a1e..adde92ad0 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -1236,8 +1236,9 @@ ([x y] (core/list 'js* "(~{} | ~{})" x y)) ([x y & more] `(bit-or (bit-or ~x ~y) ~@more))) -(core/defmacro ^::ana/numeric int [x] - `(bit-or ~x 0)) +(core/defmacro int + [x] + (core/list 'js* "(~{} | 0)" x)) (core/defmacro ^::ana/numeric bit-xor ([x y] (core/list 'js* "(~{} ^ ~{})" x y)) From cc0d6e08758e84e848e571086465bb011d5b3755 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 25 Nov 2025 06:36:02 -0500 Subject: [PATCH 73/94] remove now irrelevant test around int --- src/test/clojure/cljs/type_inference_tests.clj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/clojure/cljs/type_inference_tests.clj b/src/test/clojure/cljs/type_inference_tests.clj index 2bd0855c3..1a9170020 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -350,10 +350,12 @@ (cljs.env/with-compiler-env test-cenv (:tag (analyze test-env '(dec x))))) 'number)) - (is (= (ana/no-warn - (cljs.env/with-compiler-env test-cenv - (:tag (analyze test-env '(int x))))) - 'number)) + ;; we relaxed int, making it too strict just creates + ;; problems for legit coercion, Clojure is vague so we are vague :) + ;; (is (= (ana/no-warn + ;; (cljs.env/with-compiler-env test-cenv + ;; (:tag (analyze test-env '(int x))))) + ;; 'number)) (is (= (ana/no-warn (cljs.env/with-compiler-env test-cenv (:tag (analyze test-env '(unchecked-int x))))) From f6abdc850aa2f0a6d350e23e0719ca1e6e8c16df Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 25 Nov 2025 20:14:13 -0500 Subject: [PATCH 74/94] docstrings for all the :lite-mode ctor fns that are not intended to be used directly --- src/main/cljs/cljs/core.cljs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 40944f0c8..7d8fbeaaf 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12494,12 +12494,14 @@ reduces them without incurring seq initialization" (set! (. VectorLite -fromArray) (fn [xs] (VectorLite. nil xs nil))) (defn vector-lite + ":lite-mode version of vector, not intended to be used directly." [& args] (if (and (instance? IndexedSeq args) (zero? (.-i args))) (.fromArray VectorLite (aclone (.-arr args))) (VectorLite. nil (into-array args) nil))) (defn vec-lite + ":lite-mode version of vec, not intended to be used directly." [coll] (cond (map-entry? coll) @@ -12742,8 +12744,7 @@ reduces them without incurring seq initialization" (set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj nil))) (defn obj-map - "keyval => key val - Returns a new object map with supplied mappings." + ":lite-mode simple key hash-map, not intended to be used directly." [& keyvals] (let [ks (array) obj (js-obj)] @@ -12973,8 +12974,7 @@ reduces them without incurring seq initialization" out))))) (defn hash-map-lite - "keyval => key val - Returns a new hash map with supplied mappings." + ":lite-mode version of hash-map, not intended to be used directly." [& keyvals] (loop [in (seq keyvals), out (. HashMapLite -EMPTY)] (if in @@ -13095,6 +13095,7 @@ reduces them without incurring seq initialization" (set! (. SetLite -EMPTY) (SetLite. nil (. HashMapLite -EMPTY) empty-unordered-hash)) (defn set-lite + ":lite-mode version of set, not intended ot be used directly." [coll] (if (set? coll) (-with-meta coll nil) From 5a26a08265b67f327f525ddbdac9920f4a041359 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Tue, 25 Nov 2025 23:03:22 -0500 Subject: [PATCH 75/94] add proxy tests to main test runner and lite mode runner (#295) --- src/test/cljs/cljs/lite_collections_test.cljs | 2 ++ src/test/cljs/cljs/proxy_test.cljs | 33 +++++++++++++++++++ src/test/cljs/lite_test_runner.cljs | 7 ++-- src/test/cljs/test_runner.cljs | 4 ++- 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/test/cljs/cljs/proxy_test.cljs diff --git a/src/test/cljs/cljs/lite_collections_test.cljs b/src/test/cljs/cljs/lite_collections_test.cljs index 99cb2be29..56d44d0eb 100644 --- a/src/test/cljs/cljs/lite_collections_test.cljs +++ b/src/test/cljs/cljs/lite_collections_test.cljs @@ -21,6 +21,8 @@ (deftest test-set-lite-with-set (is (= (set-lite []) (set []))) (is (= (set []) (set-lite []))) + (is (= (set-lite ["foo" "bar"]) (set-lite ["foo" "bar"]))) + (is (= (set-lite ["foo" "bar"]) (set-lite #js ["foo" "bar"]))) (is (= (set-lite [(MapEntry. 1 2 nil)]) (set [(MapEntry. 1 2 nil)])))) diff --git a/src/test/cljs/cljs/proxy_test.cljs b/src/test/cljs/cljs/proxy_test.cljs new file mode 100644 index 000000000..36b53d7cd --- /dev/null +++ b/src/test/cljs/cljs/proxy_test.cljs @@ -0,0 +1,33 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.proxy-test + (:refer-global :only [Object]) + (:require [cljs.test :as test :refer-macros [deftest testing is are]] + [cljs.proxy :refer [builder]] + [goog.object :as gobj])) + +(def proxy (builder)) + +(deftest map-proxy + (let [proxied (proxy {:foo 1 :bar 2})] + (is (== 1 (gobj/get proxied "foo"))) + (is (== 2 (gobj/get proxied "bar"))) + (is (= #{"foo" "bar"} (into #{} (Object/keys proxied)))))) + +(deftest vector-proxy + (let [proxied (proxy [1 2 3 4])] + (is (== 4 (alength proxied))) + (is (== 1 (aget proxied 0))) + (is (== 4 (aget proxied 3))))) + +(comment + + (test/run-tests) + +) diff --git a/src/test/cljs/lite_test_runner.cljs b/src/test/cljs/lite_test_runner.cljs index 95313b3f8..0b0c45b56 100644 --- a/src/test/cljs/lite_test_runner.cljs +++ b/src/test/cljs/lite_test_runner.cljs @@ -7,7 +7,8 @@ ;; You must not remove this notice, or any other, from this software. (ns lite-test-runner - (:require [cljs.test :refer-macros [run-tests]] + (:require [cljs.proxy-test] + [cljs.test :refer-macros [run-tests]] [cljs.apply-test] [cljs.primitives-test] [cljs.destructuring-test] @@ -72,6 +73,7 @@ (enable-console-print!)) (run-tests + 'cljs.proxy-test 'cljs.apply-test 'cljs.primitives-test 'cljs.destructuring-test @@ -123,5 +125,4 @@ 'cljs.repl-test 'cljs.lite-collections-test 'cljs.extend-to-native-test - 'cljs.var-test - ) + 'cljs.var-test) diff --git a/src/test/cljs/test_runner.cljs b/src/test/cljs/test_runner.cljs index 7e551f9de..16a1cbf18 100644 --- a/src/test/cljs/test_runner.cljs +++ b/src/test/cljs/test_runner.cljs @@ -7,7 +7,8 @@ ;; You must not remove this notice, or any other, from this software. (ns test-runner - (:require [cljs.test :refer-macros [run-tests]] + (:require [cljs.proxy-test] + [cljs.test :refer-macros [run-tests]] [cljs.apply-test] [cljs.primitives-test] [cljs.destructuring-test] @@ -71,6 +72,7 @@ (enable-console-print!)) (run-tests + 'cljs.proxy-test 'cljs.apply-test 'cljs.primitives-test 'cljs.destructuring-test From 9f77604d532c9ee831e0b300def280b669e780a0 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Wed, 26 Nov 2025 09:24:24 -0500 Subject: [PATCH 76/94] CLJS-3464: `parents` does not walk JavaScript prototype chain (#296) - implement `bases` return immediate prototype of arg - implement `supers` returns immediate and indirect protoypes of arg - fix hierarchy code to use js-fn? where Clojure used class - add assertions to derive - uncomment some test assertions - do not mutate the root object --- src/main/cljs/cljs/core.cljs | 78 +++++++++++++++++++++++-------- src/test/cljs/cljs/core_test.cljs | 6 +-- 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 7d8fbeaaf..88e52f31d 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -1483,10 +1483,18 @@ IMeta (-meta [_] nil)) +(defn- root-obj + [] + (->> js/Function + (.getPrototypeOf js/Object) + (.getPrototypeOf js/Object))) + (extend-type default IHash (-hash [o] - (goog/getUid o))) + (if (identical? o (root-obj)) + 0 + (goog/getUid o)))) (extend-type symbol IHash @@ -11262,6 +11270,23 @@ reduces them without incurring seq initialization" (defn- swap-global-hierarchy! [f & args] (apply swap! (get-global-hierarchy) f args)) +(defn bases + "Returns the immediate prototype of c" + [c] + (when c + (let [s (.getPrototypeOf js/Object c)] + (when s + (list s))))) + +(defn supers + "Returns the immediate and indirect prototypes of c, if any" + [c] + (loop [ret (set (bases c)) cs ret] + (if (seq cs) + (let [c (first cs) bs (bases c)] + (recur (into ret bs) (into (disj cs c) bs))) + (not-empty ret)))) + (defn ^boolean isa? "Returns true if (= child parent), or child is directly or indirectly derived from parent, either via a JavaScript type inheritance relationship or a @@ -11270,17 +11295,17 @@ reduces them without incurring seq initialization" hierarchy" ([child parent] (isa? @(get-global-hierarchy) child parent)) ([h child parent] - (or (= child parent) - ;; (and (class? parent) (class? child) - ;; (. ^Class parent isAssignableFrom child)) - (contains? ((:ancestors h) child) parent) - ;;(and (class? child) (some #(contains? ((:ancestors h) %) parent) (supers child))) - (and (vector? parent) (vector? child) - (== (count parent) (count child)) - (loop [ret true i 0] - (if (or (not ret) (== i (count parent))) - ret - (recur (isa? h (child i) (parent i)) (inc i)))))))) + (or (= child parent) + (and (js-fn? parent) (js-fn? child) + (instance? parent child)) + (contains? ((:ancestors h) child) parent) + (and (js-fn? child) (some #(contains? ((:ancestors h) %) parent) (supers child))) + (and (vector? parent) (vector? child) + (== (count parent) (count child)) + (loop [ret true i 0] + (if (or (not ret) (== i (count parent))) + ret + (recur (isa? h (child i) (parent i)) (inc i)))))))) (defn parents "Returns the immediate parents of tag, either via a JavaScript type @@ -11288,7 +11313,12 @@ reduces them without incurring seq initialization" must be a hierarchy obtained from make-hierarchy, if not supplied defaults to the global hierarchy" ([tag] (parents @(get-global-hierarchy) tag)) - ([h tag] (not-empty (get (:parents h) tag)))) + ([h tag] + (not-empty + (let [tp (get (:parents h) tag)] + (if (js-fn? tag) + (into (set (bases tag)) tp) + tp))))) (defn ancestors "Returns the immediate and indirect parents of tag, either via a JavaScript type @@ -11296,7 +11326,15 @@ reduces them without incurring seq initialization" must be a hierarchy obtained from make-hierarchy, if not supplied defaults to the global hierarchy" ([tag] (ancestors @(get-global-hierarchy) tag)) - ([h tag] (not-empty (get (:ancestors h) tag)))) + ([h tag] + (not-empty + (let [ta (get (:ancestors h) tag)] + (if (js-fn? tag) + (let [superclasses (set (supers tag))] + (reduce into superclasses + (cons ta + (map #(get (:ancestors h) %) superclasses)))) + ta))))) (defn descendants "Returns the immediate and indirect children of tag, through a @@ -11305,7 +11343,10 @@ reduces them without incurring seq initialization" hierarchy. Note: does not work on JavaScript type inheritance relationships." ([tag] (descendants @(get-global-hierarchy) tag)) - ([h tag] (not-empty (get (:descendants h) tag)))) + ([h tag] + (if (js-fn? tag) + (throw (js/Error. "Can't get descendants of constructors")) + (not-empty (get (:descendants h) tag))))) (defn derive "Establishes a parent/child relationship between parent and @@ -11315,13 +11356,12 @@ reduces them without incurring seq initialization" supplied defaults to, and modifies, the global hierarchy." ([tag parent] (assert (namespace parent)) - ;; (assert (or (class? tag) (and (instance? cljs.core.Named tag) (namespace tag)))) + (assert (or (js-fn? tag) (and (implements? INamed tag) (namespace tag)))) (swap-global-hierarchy! derive tag parent) nil) ([h tag parent] (assert (not= tag parent)) - ;; (assert (or (class? tag) (instance? clojure.lang.Named tag))) - ;; (assert (instance? clojure.lang.INamed tag)) - ;; (assert (instance? clojure.lang.INamed parent)) + (assert (or (js-fn? tag) (implements? INamed tag))) + (assert (implements? INamed parent)) (let [tp (:parents h) td (:descendants h) ta (:ancestors h) diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index 3cd24205a..2286c2cef 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -197,12 +197,12 @@ (is (= #{:cljs.core-test/rect :cljs.core-test/square} (descendants ::shape))) (is (true? (isa? 42 42))) (is (true? (isa? ::square ::shape))) - ;(derive ObjMap ::collection) + (derive ObjMap ::collection) (derive cljs.core.PersistentHashSet ::collection) - ;(is (true? (isa? ObjMap ::collection))) + (is (true? (isa? ObjMap ::collection))) (is (true? (isa? cljs.core.PersistentHashSet ::collection))) (is (false? (isa? cljs.core.IndexedSeq ::collection))) - ;; ?? (isa? String Object) + (isa? js/String js/Object) (is (true? (isa? [::square ::rect] [::shape ::shape]))) ;; ?? (ancestors java.util.ArrayList) ;; ?? isa? based dispatch tests From 8e6f05288fda6faa604202d9114cdccae6d7c211 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Fri, 28 Nov 2025 07:14:16 -0500 Subject: [PATCH 77/94] CLJS-3466: support qualified method in return position (#297) Co-authored-by: Michiel Borkent --- src/main/clojure/cljs/analyzer.cljc | 2 +- src/test/cljs/cljs/qualified_method_test.cljs | 17 +++++++++++++++++ src/test/cljs/lite_test_runner.cljs | 4 +++- src/test/cljs/test_runner.cljs | 4 +++- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/test/cljs/cljs/qualified_method_test.cljs diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index f1c337420..fc3fc4321 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -4141,7 +4141,7 @@ {:op :qualified-method :env env :form sym - :class (analyze-symbol env (symbol sym-ns))} + :class (analyze-symbol (assoc env :context :expr) (symbol sym-ns))} (if (= "new" sym-name) {:kind :new :name (symbol sym-name)} diff --git a/src/test/cljs/cljs/qualified_method_test.cljs b/src/test/cljs/cljs/qualified_method_test.cljs new file mode 100644 index 000000000..df339b6b8 --- /dev/null +++ b/src/test/cljs/cljs/qualified_method_test.cljs @@ -0,0 +1,17 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.qualified-method-test + (:refer-global :only [String]) + (:require [cljs.test :as test :refer-macros [deftest testing is]])) + +(deftest qualified-method-return-position-test + (testing "qualified method returned from function to force it in return position" + (let [f (fn [] String/.toUpperCase) + m (f)] + (is (= "FOO" (m "foo")))))) diff --git a/src/test/cljs/lite_test_runner.cljs b/src/test/cljs/lite_test_runner.cljs index 0b0c45b56..c9f58d677 100644 --- a/src/test/cljs/lite_test_runner.cljs +++ b/src/test/cljs/lite_test_runner.cljs @@ -7,7 +7,8 @@ ;; You must not remove this notice, or any other, from this software. (ns lite-test-runner - (:require [cljs.proxy-test] + (:require [cljs.qualified-method-test] + [cljs.proxy-test] [cljs.test :refer-macros [run-tests]] [cljs.apply-test] [cljs.primitives-test] @@ -73,6 +74,7 @@ (enable-console-print!)) (run-tests + 'cljs.qualified-method-test 'cljs.proxy-test 'cljs.apply-test 'cljs.primitives-test diff --git a/src/test/cljs/test_runner.cljs b/src/test/cljs/test_runner.cljs index 16a1cbf18..666e0ea82 100644 --- a/src/test/cljs/test_runner.cljs +++ b/src/test/cljs/test_runner.cljs @@ -7,7 +7,8 @@ ;; You must not remove this notice, or any other, from this software. (ns test-runner - (:require [cljs.proxy-test] + (:require [cljs.qualified-method-test] + [cljs.proxy-test] [cljs.test :refer-macros [run-tests]] [cljs.apply-test] [cljs.primitives-test] @@ -72,6 +73,7 @@ (enable-console-print!)) (run-tests + 'cljs.qualified-method-test 'cljs.proxy-test 'cljs.apply-test 'cljs.primitives-test From 5bb99126d7eecfde4bf1514c00d2f9ea5e11ab38 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 30 Nov 2025 09:35:55 -0500 Subject: [PATCH 78/94] add Browser REPL alias to deps.edn --- deps.edn | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deps.edn b/deps.edn index 7651fd0c1..b7057d93f 100644 --- a/deps.edn +++ b/deps.edn @@ -11,6 +11,8 @@ :aliases {:cljs-repl {:extra-paths ["src/test/cljs"] :main-opts ["-m" "cljs.main" "-re" "node" "-d" ".cljs_repl" "-r"]} + :cljs-brepl {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-d" ".cljs_brepl" "-r"]} :cljs-lite-repl {:extra-paths ["src/test/cljs"] :main-opts ["-m" "cljs.main" "-co" "{:lite-mode true}" "-re" "node" "-d" ".cljs_lite_repl" "-r"]} :cli.test.run {:extra-paths ["src/test/cljs_cli"] From 9a55107a23f6d8fe1a9c8f5ece623f6fa1c6aeab Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 30 Nov 2025 15:15:51 -0500 Subject: [PATCH 79/94] cljs.proxy doesn't handle `for .. of` correctly (#298) - check for Symbol.iterator case in vec and map handler - need to bind iterator to target for it to work - add cljs.proxy.impl/MapIterator * apply the proxy ctor to the iterator results --- src/main/cljs/cljs/proxy.cljs | 24 +++++++++++++++++++----- src/main/cljs/cljs/proxy/impl.cljs | 9 +++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index b0d40655f..c3ef55414 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -1,6 +1,6 @@ (ns cljs.proxy - (:refer-global :only [Proxy isNaN]) - (:require [cljs.proxy.impl :refer [SimpleCache]])) + (:refer-global :only [isNaN Proxy Symbol]) + (:require [cljs.proxy.impl :refer [SimpleCache MapIterator]])) (defn- write-through [f] (let [cache (SimpleCache. #js {} 0)] @@ -45,16 +45,28 @@ (js* "var __ctor") (let [cache-key-fn (write-through key-fn) vec-handler #js {:get (fn [^cljs.core/IIndexed target prop receiver] - (if (identical? prop "length") + (cond + (identical? "length" prop) (-count ^cljs.core/ICounted target) + + (identical? (. Symbol -iterator) prop) + (fn [] + (MapIterator. + ((.bind (unchecked-get target prop) target)) js/__ctor)) + + :else (let [n (js* "+~{}" prop)] (when (and (number? n) (not (isNaN n))) (js/__ctor (-nth target n nil)))))) :has (fn [^cljs.core/IAssociative target prop] - (if (identical? prop "length") - true + (cond + (identical? prop "length") true + + (identical? (. Symbol -iterator) prop) true + + :else (let [n (js* "+~{}" prop)] (and (number? n) (not (isNaN n)) @@ -150,5 +162,7 @@ (def proxied-deep (proxy [{:foo "Hello"}])) (-> proxied-deep (aget 0) (unchecked-get "foo")) + + (aget ((cljs.proxy/builder) [{}]) 0) ) diff --git a/src/main/cljs/cljs/proxy/impl.cljs b/src/main/cljs/cljs/proxy/impl.cljs index 56c99430d..3914cc115 100644 --- a/src/main/cljs/cljs/proxy/impl.cljs +++ b/src/main/cljs/cljs/proxy/impl.cljs @@ -13,3 +13,12 @@ (clear [this] (set! obj #js {}) (set! cnt 0))) + +(deftype MapIterator [^:mutable iter f] + Object + (next [_] + (let [x (.next iter)] + (if-not ^boolean (. x -done) + #js {:value (f (. x -value)) + :done false} + x)))) From df70c1a4c55da1dc49b2e1ec84f75af11a7e1809 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 30 Nov 2025 16:25:11 -0500 Subject: [PATCH 80/94] bootstrap wasn't updated for cljs.compiler/emit-global-export change (#299) --- src/main/cljs/cljs/js.cljs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/cljs/cljs/js.cljs b/src/main/cljs/cljs/js.cljs index 076350951..bb8e7fbc4 100644 --- a/src/main/cljs/cljs/js.cljs +++ b/src/main/cljs/cljs/js.cljs @@ -651,14 +651,14 @@ (.append sb "null;"))) (defn- global-exports-side-effects - [bound-vars sb deps ns-name emit-nil-result?] + [bound-vars sb deps ns-name opts] (let [{:keys [js-dependency-index]} @(:*compiler* bound-vars)] (doseq [dep deps] (let [{:keys [global-exports]} (get js-dependency-index (name dep))] (.append sb (with-out-str - (comp/emit-global-export ns-name global-exports dep))))) - (when (and (seq deps) emit-nil-result?) + (comp/emit-global-export ns-name global-exports dep opts))))) + (when (and (seq deps) (:def-emits-var opts)) (.append sb "null;")))) (defn- trampoline-safe @@ -835,8 +835,7 @@ (node-side-effects bound-vars sb node-deps ns-name (:def-emits-var opts))) (global-exports-side-effects bound-vars sb (filter ana/dep-has-global-exports? (:deps ast)) - ns-name - (:def-emits-var opts)) + ns-name opts) (cb (try {:ns ns-name :value ((:*eval-fn* bound-vars) {:source (.toString sb)})} (catch :default cause @@ -1102,8 +1101,7 @@ (node-side-effects bound-vars sb node-deps ns-name (:def-emits-var opts))) (global-exports-side-effects bound-vars sb (filter ana/dep-has-global-exports? (:deps ast)) - ns-name - (:def-emits-var opts)) + ns-name opts) (trampoline compile-loop ns')))))) (do (env/with-compiler-env (assoc @(:*compiler* bound-vars) :options opts) From e3065b3ad6a8223d5e12203a85cb794b0782cc57 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 30 Nov 2025 19:00:19 -0500 Subject: [PATCH 81/94] update changes.md --- changes.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/changes.md b/changes.md index 55c6119e2..f41315f45 100644 --- a/changes.md +++ b/changes.md @@ -1,3 +1,16 @@ +## 1.12.1NN + +### Changes +* Be less specific about the behavior of integer coercion fns + +### Fixes +* Docstrings for `:lite-mode` support fns +* CLJS-3456: bootstrap wasn't updated for cljs.compiler/emit-global-export change +* cljs.proxy doesn't handle `for .. of` correctly +* CLJS-3466: support qualified method in return position +* CLJS-3464: `parents` does not walk JavaScript prototype chain +* CLJS-3463: rename all the lite mode data structures / fns to avoid clashing + ## 1.12.116 ### Changes From 54b1e3397a5824a68b11bd4189de2dbbdc5fbbc9 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 30 Nov 2025 20:29:06 -0500 Subject: [PATCH 82/94] allow cljs.proxy cache customization, provide default proxy (#300) --- src/main/cljs/cljs/proxy.cljs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index c3ef55414..0940d472c 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -38,12 +38,17 @@ Note key-fn is only used for proxied ClojureScript maps. This function should map strings to the appropriate key representation. If unspecified, key-fn defaults to keyword. All maps - proxied from the same ctor fn will share the same key-fn cache." + proxied from the same ctor fn will share the same key-fn cache. + + A cache-fn may be suppled to override the default cache. This fn + should take key-fn and return a memoized version." ([] (builder keyword)) ([key-fn] + (builder keyword write-through)) + ([key-fn cache-fn] (js* "var __ctor") - (let [cache-key-fn (write-through key-fn) + (let [cache-key-fn (cache-fn key-fn) vec-handler #js {:get (fn [^cljs.core/IIndexed target prop receiver] (cond (identical? "length" prop) @@ -106,6 +111,9 @@ :else target))] __ctor))) +(def ^{:doc "Default proxy for maps and vectors."} + proxy (builder)) + (comment (def c (SimpleCache. #js {} 0)) From 6bb6df196506286994d1f4259c89ea0f5a0dffd4 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 30 Nov 2025 20:31:38 -0500 Subject: [PATCH 83/94] update changes.md for cljs.proxy changes --- changes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changes.md b/changes.md index f41315f45..d5b8d48c5 100644 --- a/changes.md +++ b/changes.md @@ -2,6 +2,8 @@ ### Changes * Be less specific about the behavior of integer coercion fns +* `cljs.proxy/builder`, `cache-fn` parameterization +* Provide `cljs.proxy/proxy` default ### Fixes * Docstrings for `:lite-mode` support fns From 9d59f911cec44a7779efaed403c9bd954f426387 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 30 Nov 2025 21:30:17 -0500 Subject: [PATCH 84/94] typos in comment --- src/main/cljs/cljs/core.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 88e52f31d..679b17925 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -3639,7 +3639,7 @@ reduces them without incurring seq initialization" IEmptyableCollection (-empty [coll] ;; MAYBE FIXME: :lite-mode testing uncovered a very old bug, empty on seq - ;; should discared the metadata, we change the behavior in LITE_MODE for now + ;; should discard the metadata, we changed the behavior in LITE_MODE for now ;; to avoid a breaking change (if-not ^boolean LITE_MODE (-with-meta (.-EMPTY List) meta) From 2bc5688b5c82121d38f35e9ab9173dda44426a25 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 30 Nov 2025 21:43:00 -0500 Subject: [PATCH 85/94] missing notices --- src/main/cljs/cljs/proxy.cljs | 8 ++++++++ src/main/cljs/cljs/proxy/impl.cljs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index 0940d472c..23a85a9f7 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -1,3 +1,11 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + (ns cljs.proxy (:refer-global :only [isNaN Proxy Symbol]) (:require [cljs.proxy.impl :refer [SimpleCache MapIterator]])) diff --git a/src/main/cljs/cljs/proxy/impl.cljs b/src/main/cljs/cljs/proxy/impl.cljs index 3914cc115..c47652931 100644 --- a/src/main/cljs/cljs/proxy/impl.cljs +++ b/src/main/cljs/cljs/proxy/impl.cljs @@ -1,3 +1,11 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + (ns cljs.proxy.impl) (deftype SimpleCache [^:mutable obj ^:mutable cnt] From 49774b1bbcd6d6a14c7aaaf73c92aa4c6ea8b96c Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 7 Dec 2025 16:42:01 -0500 Subject: [PATCH 86/94] re-order latest changes --- changes.md | 22 +++++++++++----------- src/main/clojure/cljs/analyzer.cljc | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/changes.md b/changes.md index d5b8d48c5..901bba22d 100644 --- a/changes.md +++ b/changes.md @@ -2,15 +2,15 @@ ### Changes * Be less specific about the behavior of integer coercion fns -* `cljs.proxy/builder`, `cache-fn` parameterization * Provide `cljs.proxy/proxy` default +* `cljs.proxy/builder`, `cache-fn` parameterization ### Fixes +* `cljs.proxy` doesn't handle `for .. of` correctly * Docstrings for `:lite-mode` support fns -* CLJS-3456: bootstrap wasn't updated for cljs.compiler/emit-global-export change -* cljs.proxy doesn't handle `for .. of` correctly * CLJS-3466: support qualified method in return position * CLJS-3464: `parents` does not walk JavaScript prototype chain +* CLJS-3456: bootstrap wasn't updated for cljs.compiler/emit-global-export change * CLJS-3463: rename all the lite mode data structures / fns to avoid clashing ## 1.12.116 @@ -117,7 +117,7 @@ * CLJS-3372: Vendorize data.json, transit-clj, and tools.reader data.json and transit-clj are no longer dependencies. CLJS-3375 bridges tools.reader for backwards compatibility -* Clojure 1.10 minimum version +* Clojure 1.10 minimum version * Update Google Closure Compiler, transit-java, tools.reader dependencies to latest * CLJS-2820 Compile cljs.loader regardless of whether :modules are used * CLJS-3370: improved uuid regex to only accept hex characters @@ -618,7 +618,7 @@ * cljs.main, simple command line access to Compiler & REPLs * cljs.server.* namespaces for integration with -Dclojure.server.repl * :aot-cache compiler to enable global AOT caching of dependencies in JARs -* :stable-names compiler flag, to support vendorization when using :modules, +* :stable-names compiler flag, to support vendorization when using :modules, defaults to true when using :modules. * Add :webworker & :nashorn target * pREPL implementation (usage requires Clojure 1.10.0-alpha) @@ -897,7 +897,7 @@ ### Fixes * CLJS-2139: Undeclared var regression in fn bodies -* CLJS-2137: Missing INext on some sequences +* CLJS-2137: Missing INext on some sequences * CLJS-2136: Clarify IFind contract to avoid double-lookups * need to elide :c.a/analyzed in c.a/analyze-wrap-meta to avoid dumping unintended with-meta expressions @@ -996,12 +996,12 @@ ### Changes * CLJS-2021: subvec throws when passed non-vector -* CLJS-1884: Give a chance to MetaFn to be removed by closure under :advanced +* CLJS-1884: Give a chance to MetaFn to be removed by closure under :advanced optimization Replace with-meta calls by -with-meta calls where possible * CLJS-2052: Port new spec.alpha enhancements * Update Google Closure Compiler dependency * Update Google Closure Library dependency - + ### Fixes * CLJS-2053: Regression: cljs.spec.alpha/any for fdef * CLJS-2039: remove extraneous argument from ChunkBuffer.chunk @@ -1049,7 +1049,7 @@ ### Changes * CLJS-2006: Upgrade Closure Compiler to April 2017 release -### Fixes +### Fixes * CLJS-1497: `find` on an associative collection does not return collection key * CLJS-1996: Support correct checking of :preloads when :optimizations not specified * CLJS-1994: assoc on nil returns PHM (expected PAM) @@ -1378,7 +1378,7 @@ possible * CLJS-1661: cljs.spec: non-spec'ed fn var printing * compute read/write opts for transit if possible, handle JSValue * CLJS-1660: cljs.spec: Always return var from instrument / unstrument -* CLJS-1671: Bad cljs.spec interactive instrumentation session +* CLJS-1671: Bad cljs.spec interactive instrumentation session * CLJS-1664: The filename aux.cljs is a problem on windows. * CLJS-1667: bad describe* for and-spec-impl * CLJS-1699: Self-host: s/fdef ns-qualify *ns* name field access @@ -1753,7 +1753,7 @@ determine which version you should use. * CLJS-1203: standard way to pass multiple directories to build ### Fixes -* CLJS-1216: incorrect max fixed arity for fns both multi-arity and variadic +* CLJS-1216: incorrect max fixed arity for fns both multi-arity and variadic * cljs.analyzer/parse-ns did not bind *cljs-file* * CLJS-1201: compare broken for IIndexed collections * CLJS-1202: cljs.repl/load-file is not additive diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index fc3fc4321..12940689a 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1067,7 +1067,7 @@ ;; i.e. [console] -> [Console] but :tag is Console _not_ Function vs. ;; [console log] -> [Console prototype log] where :tag is Function (and (empty? (next pre)) - (not (contains? ret :info))) +x (not (contains? ret :info))) (assoc :info info'))] ;; handle actual occurrences of types, i.e. `Console` (if (and (or (:ctor info') (:iface info')) (= 'Function (:tag info'))) From c4295f303100bbf5afac449242d30bca1126f1a1 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Mon, 8 Dec 2025 19:19:37 -0500 Subject: [PATCH 87/94] 1.12.134 --- README.md | 10 +++++----- changes.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3fc82e6e0..2975a3f15 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ Official web site: https://clojurescript.org ## Releases and dependency information ## -Latest stable release: 1.12.116 +Latest stable release: 1.12.134 * [All released versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22clojurescript%22) [Clojure deps.edn](http://clojure.org/guides/deps_and_cli) dependency information: ``` - org.clojure/clojurescript {:mvn/version "1.12.116"} + org.clojure/clojurescript {:mvn/version "1.12.134"} ``` [Leiningen](https://github.com/technomancy/leiningen/) dependency information: ``` -[org.clojure/clojurescript "1.12.116"] +[org.clojure/clojurescript "1.12.134"] ``` [Maven](https://maven.apache.org) dependency information: @@ -28,7 +28,7 @@ Latest stable release: 1.12.116 org.clojure clojurescript - 1.12.116 + 1.12.134 ``` @@ -45,7 +45,7 @@ Please point all of your questions and feedback to the [Clojure mailing list](https://groups.google.com/group/clojure). There is a community run [ClojureScript user mailing list](https://groups.google.com/group/clojurescript) and -the IRC channel, `#clojurescript` on [freenode.net](https://freenode.net/), is quite active. +the IRC channel, `#clojurescript` on [freenode.net](https://freenode.net/), is quite active. There is also a community run [Slack channel](https://clojurians.slack.com). The Jira bug/feature tracking application is located at . Before submitting issues diff --git a/changes.md b/changes.md index 901bba22d..54c63d805 100644 --- a/changes.md +++ b/changes.md @@ -1,4 +1,4 @@ -## 1.12.1NN +## 1.12.134 ### Changes * Be less specific about the behavior of integer coercion fns From 3122975e70f0a612cee6aab82be31428ac810dd1 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 28 Dec 2025 08:53:11 -0500 Subject: [PATCH 88/94] CLJS-3468: :refer-global should not make unrenamed object available (#301) Co-authored-by: Michiel Borkent --- src/main/clojure/cljs/analyzer.cljc | 3 ++- src/test/clojure/cljs/analyzer_tests.clj | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 12940689a..daa4872a0 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3098,7 +3098,8 @@ x (not (contains? ret :info))) (throw (error env (str err-str (pr-str parsed-spec))))) (when-not (every? #{:only :rename} (keys parsed-spec)) (throw (error env (str err-str (pr-str parsed-spec))))) - {:use (zipmap only (repeat 'js)) + {:use (zipmap (if rename (remove rename only) + only) (repeat 'js)) :rename (into {} (map (fn [[orig new-name]] [new-name (symbol "js" (str orig))])) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index ca388182d..07aff9247 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -391,7 +391,7 @@ (let [parsed (ana/parse-global-refer-spec {} '((:refer-global :only [Date Symbol] :rename {Symbol JSSymbol})))] (is (= parsed - '{:use {Date js Symbol js} + '{:use {Date js} :rename {JSSymbol js/Symbol}})))) (deftest test-parse-require-global From c4fcc9ccd80fe30ea4bf29abdf1cbc59a3f76b67 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 28 Dec 2025 12:43:23 -0500 Subject: [PATCH 89/94] goog-define types are already inferred --- src/main/cljs/cljs/core.cljs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 679b17925..60078fcf1 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -54,8 +54,7 @@ *global* "default") (goog-define - ^{:doc "Boolean flag for LITE_MODE" - :jsdoc ["@type {boolean}"]} + ^{:doc "Boolean flag for LITE_MODE"} LITE_MODE false) (def From 8a596a8bdf8188112332207e5351e143740168f5 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Sun, 28 Dec 2025 14:30:39 -0500 Subject: [PATCH 90/94] CLJS-3469: ClojureScript :preloads doesn't work with just cljs.main --repl (#302) Co-authored-by: Juan Monetta --- src/main/clojure/cljs/cli.clj | 3 ++- src/main/clojure/cljs/repl/browser.clj | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/cljs/cli.clj b/src/main/clojure/cljs/cli.clj index da4f2761f..9087151c9 100644 --- a/src/main/clojure/cljs/cli.clj +++ b/src/main/clojure/cljs/cli.clj @@ -321,7 +321,8 @@ present" reopts (merge repl-env-options (select-keys opts [:main :output-dir])) _ (when (or ana/*verbose* (:verbose opts)) (util/debug-prn "REPL env options:" (pr-str reopts))) - renv (apply (target->repl-env (:target options) repl-env) (mapcat identity reopts))] + renv (-> (apply (target->repl-env (:target options) repl-env) (mapcat identity reopts)) + (assoc :compiler-opts opts))] (repl/repl* renv (assoc opts ::repl/fast-initial-prompt? diff --git a/src/main/clojure/cljs/repl/browser.clj b/src/main/clojure/cljs/repl/browser.clj index 2c907c6d1..9b333127e 100644 --- a/src/main/clojure/cljs/repl/browser.clj +++ b/src/main/clojure/cljs/repl/browser.clj @@ -182,7 +182,7 @@ (defn send-static [{path :path :as request} conn - {:keys [static-dir output-dir host port gzip?] :or {output-dir "out"} :as opts}] + {:keys [static-dir output-dir host port gzip? compiler-opts] :or {output-dir "out"} :as opts}] (let [output-dir (when-not (.isAbsolute (io/file output-dir)) output-dir)] (if (and static-dir (not= "/favicon.ico" path)) (let [path (if (= "/" path) "/index.html" path) @@ -224,7 +224,11 @@ clojure.browser.repl/PORT ~port} (merge (:closure-defines @browser-state)) cljsc/normalize-closure-defines - json/write-str)] + json/write-str) + preloads (when-let [preloads (:preloads compiler-opts)] + (mapv (fn [ns-symb] + (str "document.write('');")) + preloads))] (server/send-and-close conn 200 (str "var CLOSURE_UNCOMPILED_DEFINES = " closure-defines ";\n" "var CLOSURE_NO_DEPS = true;\n" @@ -233,6 +237,7 @@ (when (.exists (io/file output-dir "cljs_deps.js")) (str "document.write('');\n")) "document.write('');\n" + (when preloads (str/join "\n" preloads)) "document.write('');\n") "text/javascript" "UTF-8")) From 091df76c65478f9150e83ebbb13b4cf1097a2b2a Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 2 Feb 2026 17:43:20 -0500 Subject: [PATCH 91/94] CLJS-3471: fix printing of negative zero (#305) Co-authored-by: Michiel Borkent --- src/main/cljs/cljs/core.cljs | 1 + src/test/cljs/cljs/printing_test.cljs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 60078fcf1..c5d186689 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -10470,6 +10470,7 @@ reduces them without incurring seq initialization" (js/isNaN obj) "##NaN" (identical? obj js/Number.POSITIVE_INFINITY) "##Inf" (identical? obj js/Number.NEGATIVE_INFINITY) "##-Inf" + (js/Object.is obj -0.0) "-0.0" :else (str_ obj))) (object? obj) diff --git a/src/test/cljs/cljs/printing_test.cljs b/src/test/cljs/cljs/printing_test.cljs index c114893df..008172ed1 100644 --- a/src/test/cljs/cljs/printing_test.cljs +++ b/src/test/cljs/cljs/printing_test.cljs @@ -74,6 +74,7 @@ (is (= (pr-str 1) "1")) (is (= (pr-str -1) "-1")) (is (= (pr-str -1.5) "-1.5")) + (is (= (pr-str -0.0) "-0.0")) (is (= (pr-str [3 4]) "[3 4]")) (is (= (pr-str "foo") "\"foo\"")) (is (= (pr-str :hello) ":hello")) From 96cd8ad1405cec4a8893c09c973ca9c3c6d3355e Mon Sep 17 00:00:00 2001 From: David Nolen Date: Thu, 12 Feb 2026 20:08:45 -0500 Subject: [PATCH 92/94] CLJS-3472: str on var that is set! returns empty string (#307) * CLJS-3472: str on var that is set! returns empty string * comment out str optimization tests for now --------- Co-authored-by: Michiel Borkent --- src/main/clojure/cljs/core.cljc | 2 -- src/test/cljs/cljs/core_test.cljs | 5 +++++ src/test/clojure/cljs/build_api_tests.clj | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index adde92ad0..494f4014e 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -878,8 +878,6 @@ [& xs] (core/let [interpolate (core/fn [x] (core/cond - (typed-expr? &env x '#{clj-nil}) - nil (compile-time-constant? x) ["+~{}" x] :else diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index 2286c2cef..01f6f0b52 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -1988,6 +1988,11 @@ (testing "object is stringified using toString" (is (= "correct6\"foobar\"1:foo" (str-fn nil (+ 1 2 3))))))) +(def test-cljs-3472-var nil) +(deftest test-cljs-3472 + (set! test-cljs-3472-var "dude") + (is (= "dude" (str test-cljs-3472-var)))) + (deftest test-cljs-3425 (testing "Incorrect min/max handling of ##NaN" (is (NaN? (min ##NaN 1))) diff --git a/src/test/clojure/cljs/build_api_tests.clj b/src/test/clojure/cljs/build_api_tests.clj index 3e44d1b4b..caa8da41f 100644 --- a/src/test/clojure/cljs/build_api_tests.clj +++ b/src/test/clojure/cljs/build_api_tests.clj @@ -941,7 +941,7 @@ (test/delete-node-modules) (test/delete-out-files out)))) -(deftest test-cljs-3452-str-optimizations +#_(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) From dfdf09113756ec28e1b524d32baab9967655f589 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Sat, 14 Feb 2026 13:12:21 +0100 Subject: [PATCH 93/94] CLJS-3470: add async/await support --- resources/test.edn | 2 +- src/main/cljs/cljs/test.cljc | 2 +- src/main/clojure/cljs/analyzer.cljc | 6 + src/main/clojure/cljs/compiler.cljc | 86 +++++---- src/main/clojure/cljs/core.cljc | 118 ++++++------ src/test/cljs/cljs/async_await_test.cljs | 231 +++++++++++++++++++++++ src/test/cljs/cljs/macro_test/macros.clj | 4 + src/test/cljs/test_runner.cljs | 4 +- 8 files changed, 357 insertions(+), 96 deletions(-) create mode 100644 src/test/cljs/cljs/async_await_test.cljs diff --git a/resources/test.edn b/resources/test.edn index a8f258139..a9b40633e 100644 --- a/resources/test.edn +++ b/resources/test.edn @@ -9,7 +9,7 @@ :npm-deps {:lodash "4.17.4"} :closure-warnings {:non-standard-jsdoc :off :global-this :off} :install-deps true - :language-out :es5 + :language-out :es6 :foreign-libs [{:file "src/test/cljs/calculator_global.js" :provides ["calculator"] diff --git a/src/main/cljs/cljs/test.cljc b/src/main/cljs/cljs/test.cljc index 5afb03ca2..27776ad4b 100644 --- a/src/main/cljs/cljs/test.cljc +++ b/src/main/cljs/cljs/test.cljc @@ -262,7 +262,7 @@ cljs.test/IAsyncTest cljs.core/IFn (~'-invoke [_# ~done] - ~@body))) + ((^:async fn [] ~@body))))) ;; ============================================================================= ;; Running Tests diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index daa4872a0..c63b857f2 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -2314,6 +2314,12 @@ x (not (contains? ret :info))) meths) locals (:locals env) name-var (fn-name-var env locals name) + async (or + ;; NOTE: adding async on fn form turns it into a MetaFn which isn't great for interop, let's discourage it - Michiel Borkent + #_(:async (meta form)) + (:async (meta name)) + (:async (meta (first form)))) + env (assoc env :async async) env (if (some? name) (update-in env [:fn-scope] conj name-var) env) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index 6e9d152ff..1fbf54ec2 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -701,10 +701,16 @@ (emitln then "} else {") (emitln else "}")))))) +(defn iife-open [{:keys [async]}] + (str (when async "(await ") "(" (when async "async ") "function (){")) + +(defn iife-close [{:keys [async]}] + (str "})()" (when async ")"))) + (defmethod emit* :case [{v :test :keys [nodes default env]}] (when (= (:context env) :expr) - (emitln "(function(){")) + (emitln (iife-open env))) (let [gs (gensym "caseval__")] (when (= :expr (:context env)) (emitln "var " gs ";")) @@ -723,12 +729,13 @@ (emitln default))) (emitln "}") (when (= :expr (:context env)) - (emitln "return " gs ";})()")))) + (emitln "return " gs ";" + (iife-close env))))) (defmethod emit* :throw [{throw :exception :keys [env]}] (if (= :expr (:context env)) - (emits "(function(){throw " throw "})()") + (emits (iife-open env) "throw " throw (iife-close env)) (emitln "throw " throw ";"))) (def base-types @@ -865,7 +872,7 @@ (when (= :return (:context env)) (emitln "return (")) (when (:def-emits-var env) - (emitln "(function (){")) + (emitln (iife-open env))) (emits var) (when init (emits " = " @@ -878,7 +885,8 @@ {:op :the-var :env (assoc env :context :expr)} var-ast)) - (emitln ");})()")) + (emitln ");" + (iife-close env))) (when (= :return (:context env)) (emitln ")")) ;; NOTE: JavaScriptCore does not like this under advanced compilation @@ -936,18 +944,19 @@ (defn emit-fn-method [{expr :body :keys [type name params env recurs]}] - (emit-wrap env - (emits "(function " (munge name) "(") - (emit-fn-params params) - (emitln "){") - (when type - (emitln "var self__ = this;")) - (when recurs (emitln "while(true){")) - (emits expr) - (when recurs - (emitln "break;") - (emitln "}")) - (emits "})"))) + (let [async (:async env)] + (emit-wrap env + (emits "(" (when async "async ") "function " (munge name) "(") + (emit-fn-params params) + (emitln "){") + (when type + (emitln "var self__ = this;")) + (when recurs (emitln "while(true){")) + (emits expr) + (when recurs + (emitln "break;") + (emitln "}")) + (emits "})")))) (defn emit-arguments-to-array "Emit code that copies function arguments into an array starting at an index. @@ -968,9 +977,10 @@ (emit-wrap env (let [name (or name (gensym)) mname (munge name) - delegate-name (str mname "__delegate")] + delegate-name (str mname "__delegate") + async (:async env)] (emitln "(function() { ") - (emits "var " delegate-name " = function (") + (emits "var " delegate-name " = " (when async "async ") "function (") (doseq [param params] (emit param) (when-not (= param (last params)) (emits ","))) @@ -984,10 +994,11 @@ (emitln "}")) (emitln "};") - (emitln "var " mname " = function (" (comma-sep - (if variadic - (concat (butlast params) ['var_args]) - params)) "){") + (emitln "var " mname " = " (when async "async ") "function (" + (comma-sep + (if variadic + (concat (butlast params) ['var_args]) + params)) "){") (when type (emitln "var self__ = this;")) (when variadic @@ -1024,13 +1035,14 @@ (when (or in-loop (seq recur-params)) (mapcat :params loop-lets))) (map munge) - seq)] + seq) + async (:async env)] (when loop-locals (when (= :return (:context env)) (emits "return ")) (emitln "((function (" (comma-sep (map munge loop-locals)) "){") (when-not (= :return (:context env)) - (emits "return "))) + (emits "return "))) (if (= 1 (count methods)) (if variadic (emit-variadic-fn-method (assoc (first methods) :name name)) @@ -1054,9 +1066,10 @@ (emit-variadic-fn-method meth) (emit-fn-method meth)) (emitln ";")) - (emitln mname " = function(" (comma-sep (if variadic - (concat (butlast maxparams) ['var_args]) - maxparams)) "){") + (emitln mname " = " (when async "async ") "function(" + (comma-sep (if variadic + (concat (butlast maxparams) ['var_args]) + maxparams)) "){") (when variadic (emits "var ") (emit (last maxparams)) @@ -1101,10 +1114,10 @@ (defmethod emit* :do [{:keys [statements ret env]}] (let [context (:context env)] - (when (and (seq statements) (= :expr context)) (emitln "(function (){")) + (when (and (seq statements) (= :expr context)) (emitln (iife-open env))) (doseq [s statements] (emitln s)) (emit ret) - (when (and (seq statements) (= :expr context)) (emitln "})()")))) + (when (and (seq statements) (= :expr context)) (emitln (iife-close env))))) (defmethod emit* :try [{try :body :keys [env catch name finally]}] @@ -1112,7 +1125,7 @@ (if (or name finally) (do (when (= :expr context) - (emits "(function (){")) + (emits (iife-open env))) (emits "try{" try "}") (when name (emits "catch (" (munge name) "){" catch "}")) @@ -1120,13 +1133,14 @@ (assert (not= :const (:op (ana/unwrap-quote finally))) "finally block cannot contain constant") (emits "finally {" finally "}")) (when (= :expr context) - (emits "})()"))) + (emits (iife-close env)))) (emits try)))) (defn emit-let [{expr :body :keys [bindings env]} is-loop] (let [context (:context env)] - (when (= :expr context) (emits "(function (){")) + (when (= :expr context) + (emits (iife-open env))) (binding [*lexical-renames* (into *lexical-renames* (when (= :statement context) @@ -1145,7 +1159,7 @@ (when is-loop (emitln "break;") (emitln "}"))) - (when (= :expr context) (emits "})()")))) + (when (= :expr context) (emits (iife-close env))))) (defmethod emit* :let [ast] (emit-let ast false)) @@ -1166,11 +1180,11 @@ (defmethod emit* :letfn [{expr :body :keys [bindings env]}] (let [context (:context env)] - (when (= :expr context) (emits "(function (){")) + (when (= :expr context) (emits (iife-open env))) (doseq [{:keys [init] :as binding} bindings] (emitln "var " (munge binding) " = " init ";")) (emits expr) - (when (= :expr context) (emits "})()")))) + (when (= :expr context) (emits (iife-close env))))) (defn protocol-prefix [psym] (symbol (str (-> (str psym) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 494f4014e..d8a2d0fd5 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -7,7 +7,7 @@ ; You must not remove this notice, or any other, from this software. (ns cljs.core - (:refer-clojure :exclude [-> ->> .. amap and areduce alength aclone assert assert-args binding bound-fn case comment + (:refer-clojure :exclude [-> ->> .. amap and areduce alength aclone assert await binding bound-fn case comment cond condp declare definline definterface defmethod defmulti defn defn- defonce defprotocol defrecord defstruct deftype delay destructure doseq dosync dotimes doto extend-protocol extend-type fn for future gen-class gen-interface @@ -246,28 +246,26 @@ [p & specs] (emit-extend-protocol p specs))) -#?(:cljs - (core/defn ^{:private true} - maybe-destructured - [params body] - (if (every? core/symbol? params) - (cons params body) - (core/loop [params params - new-params (with-meta [] (meta params)) - lets []] - (if params - (if (core/symbol? (first params)) - (recur (next params) (conj new-params (first params)) lets) - (core/let [gparam (gensym "p__")] - (recur (next params) (conj new-params gparam) - (core/-> lets (conj (first params)) (conj gparam))))) - `(~new-params - (let ~lets - ~@body))))))) - -#?(:cljs - (core/defmacro fn - "params => positional-params* , or positional-params* & rest-param +(core/defn ^{:private true} + maybe-destructured + [params body] + (if (every? core/symbol? params) + (cons params body) + (core/loop [params params + new-params (with-meta [] (meta params)) + lets []] + (if params + (if (core/symbol? (first params)) + (recur (next params) (conj new-params (first params)) lets) + (core/let [gparam (gensym "p__")] + (recur (next params) (conj new-params gparam) + (core/-> lets (conj (first params)) (conj gparam))))) + `(~new-params + (let ~lets + ~@body)))))) + +(core/defmacro fn + "params => positional-params* , or positional-params* & rest-param positional-param => binding-form rest-param => binding-form binding-form => name, or destructuring-form @@ -275,35 +273,35 @@ Defines a function See https://clojure.org/reference/special_forms#fn for more information" - {:forms '[(fn name? [params*] exprs*) (fn name? ([params*] exprs*) +)]} - [& sigs] - (core/let [name (if (core/symbol? (first sigs)) (first sigs) nil) - sigs (if name (next sigs) sigs) - sigs (if (vector? (first sigs)) - (core/list sigs) - (if (seq? (first sigs)) - sigs - ;; Assume single arity syntax - (throw (js/Error. - (if (seq sigs) - (core/str "Parameter declaration " - (core/first sigs) - " should be a vector") - (core/str "Parameter declaration missing")))))) - psig (fn* [sig] + {:forms '[(fn name? [params*] exprs*) (fn name? ([params*] exprs*) +)]} + [& sigs] + (core/let [name (if (core/symbol? (first sigs)) (first sigs) nil) + sigs (if name (next sigs) sigs) + sigs (if (vector? (first sigs)) + (core/list sigs) + (if (seq? (first sigs)) + sigs + ;; Assume single arity syntax + (throw (#?(:clj Exception. :cljs js/Error.) + (if (seq sigs) + (core/str "Parameter declaration " + (core/first sigs) + " should be a vector") + (core/str "Parameter declaration missing")))))) + psig (fn* [sig] ;; Ensure correct type before destructuring sig (core/when (not (seq? sig)) - (throw (js/Error. - (core/str "Invalid signature " sig - " should be a list")))) + (throw (#?(:clj Exception. :cljs js/Error.) + (core/str "Invalid signature " sig + " should be a list")))) (core/let [[params & body] sig _ (core/when (not (vector? params)) - (throw (js/Error. - (if (seq? (first sigs)) - (core/str "Parameter declaration " params - " should be a vector") - (core/str "Invalid signature " sig - " should be a list"))))) + (throw (#?(:clj Exception. :cljs js/Error.) + (if (seq? (first sigs)) + (core/str "Parameter declaration " params + " should be a vector") + (core/str "Invalid signature " sig + " should be a list"))))) conds (core/when (core/and (next body) (map? (first body))) (first body)) body (if conds (next body) body) @@ -319,15 +317,17 @@ body) body (if pre (concat (map (fn* [c] `(assert ~c)) pre) - body) + body) body)] (maybe-destructured params body))) - new-sigs (map psig sigs)] - (with-meta - (if name - (list* 'fn* name new-sigs) - (cons 'fn* new-sigs)) - (meta &form))))) + new-sigs (map psig sigs) + fn-sym-meta (meta (first &form)) + fn*-sym (with-meta 'fn* fn-sym-meta)] + (with-meta + (if name + (list* fn*-sym name new-sigs) + (cons fn*-sym new-sigs)) + (meta &form)))) #?(:cljs (core/defmacro defn- @@ -972,6 +972,10 @@ (reduce core/str "")) " */\n")))) +(core/defmacro await [expr] + (core/assert (:async &env) "await can only be used in async contexts") + (core/list 'js* "(await ~{})" expr)) + (core/defmacro unsafe-cast "EXPERIMENTAL: Subject to change. Unsafely cast a value to a different type." [t x] @@ -3209,7 +3213,7 @@ (. self# (~(get-delegate) (seq ~restarg))))))))] `(do (set! (. ~sym ~(get-delegate-prop)) - (fn (~(vec sig) ~@body))) + (~(with-meta `fn (meta sym)) (~(vec sig) ~@body))) ~@(core/when solo `[(set! (. ~sym ~'-cljs$lang$maxFixedArity) ~(core/dec (count sig)))]) @@ -3290,7 +3294,7 @@ {:variadic? false :fixed-arity (count sig)}) ~(symbol (core/str "-cljs$core$IFn$_invoke$arity$" (count sig)))) - (fn ~method))))] + (~(with-meta `fn (core/meta name)) ~method))))] (core/let [rname (symbol (core/str ana/*cljs-ns*) (core/str name)) arglists (map first fdecl) macro? (:macro meta) diff --git a/src/test/cljs/cljs/async_await_test.cljs b/src/test/cljs/cljs/async_await_test.cljs new file mode 100644 index 000000000..75ffb6758 --- /dev/null +++ b/src/test/cljs/cljs/async_await_test.cljs @@ -0,0 +1,231 @@ +(ns cljs.async-await-test + (:require [clojure.test :refer [deftest is async]] + [cljs.core :as cc :refer [await] :rename {await aw}]) + (:require-macros [cljs.macro-test.macros :refer [await!] :as macros])) + +(defn ^:async foo [n] + (let [x (await (js/Promise.resolve 10)) + y (let [y (await (js/Promise.resolve 20))] + (inc y)) + ;; not async + f (fn [] 20)] + (+ n x y (f)))) + +(deftest defn-test + (async done + (try + (let [v (await (foo 10))] + (is (= 61 v))) + (let [v (await (apply foo [10]))] + (is (= 61 v))) + (catch :default _ (is false)) + (finally (done))))) + +(defn ^:async variadic-foo [n & ns] + (let [x (await (js/Promise.resolve n)) + y (let [y (await (js/Promise.resolve (apply + ns)))] + (inc y)) + ;; not async + f (fn [] 20)] + (+ n x y (f)))) + +(deftest variadic-defn-test + (async done + (try + (let [v (await (variadic-foo 10))] + (is (= 41 v))) + (let [v (await (variadic-foo 10 1 2 3))] + (is (= 47 v))) + (let [v (await (apply variadic-foo [10 1 2 3]))] + (is (= 47 v))) + (catch :default _ (is false)) + (finally (done))))) + +(defn ^:async multi-arity-foo + ([n] (await n)) + ([n x] (+ (await n) x))) + +(deftest multi-arity-defn-test + (async done + (try + (let [v (await (multi-arity-foo 10))] + (is (= 10 v))) + (let [v (await (multi-arity-foo 10 20))] + (is (= 30 v))) + (let [v (await (apply multi-arity-foo [10]))] + (is (= 10 v))) + (let [v (await (apply multi-arity-foo [10 20]))] + (is (= 30 v))) + (catch :default _ (is false)) + (finally (done))))) + +(defn ^:async multi-arity-variadic-foo + ([n] (await n)) + ([n & xs] (apply + (await n) xs))) + +(deftest multi-arity-variadic-test + (async done + (try + (let [v (await (multi-arity-variadic-foo 10))] + (is (= 10 v))) + (let [v (await (multi-arity-variadic-foo 10 20))] + (is (= 30 v))) + (let [v (await (apply multi-arity-variadic-foo [10]))] + (is (= 10 v))) + (let [v (await (apply multi-arity-variadic-foo [10 20]))] + (is (= 30 v))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest fn-test + (async done + (try + (let [f (^:async fn [x] (+ x (await (js/Promise.resolve 20)))) + v (await (f 10)) + v2 (await (apply f [10]))] + (is (= 30 v v2))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest varargs-fn-test + (async done + (try + (let [f (^:async fn [x & xs] (apply + x (await (js/Promise.resolve 20)) xs)) + v (await (f 10)) + v2 (await (apply f [10])) + v3 (await (f 5 5)) + v4 (await (apply f [5 5]))] + (is (= 30 v v2 v3 v4))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest variadic-fn-test + (async done + (try (let [f (^:async fn + ([x] (await (js/Promise.resolve x))) + ([x y] (cons (await (js/Promise.resolve x)) [y])))] + (is (= [1 1 [1 2] [1 2]] + [(await (f 1)) + (await (apply f [1])) + (await (f 1 2)) + (await (apply f [1 2]))]))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest variadic-varargs-fn-test + (async done + (try (let [f (^:async fn + ([x] (await (js/Promise.resolve x))) + ([x & xs] (cons (await (js/Promise.resolve x)) xs)))] + (is (= [1 1 [1 2 3] [1 2 3]] + [(await (f 1)) + (await (apply f [1])) + (await (f 1 2 3)) + (await (apply f [1 2 3]))]))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-in-throw-test + (async done + (let [f (^:async fn [x] (inc (if (odd? x) (throw (await (js/Promise.resolve "dude"))) x)))] + (try + (let [x (await (f 2))] + (is (= 3 x))) + (let [x (try (await (f 1)) + (catch :default e e))] + (is (= "dude" x))) + (catch :default _ (is false)) + (finally (done)))))) + +(deftest await-in-do-test + (async done + (try + (let [a (atom 0) + f (^:async fn [] (let [_ (do (swap! a inc) + (swap! a + (await (js/Promise.resolve 2))))] + @a)) + v (await (f))] + (is (= 3 v))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-let-fn-test + (async done + (try + (let [f (^:async fn [] (let [v + ;; force letfn in expr position + (letfn [(^:async f [] (inc (await (js/Promise.resolve 10))))] + (inc (await (f))))] + (identity v))) + v (await (f))] + (is (= 12 v))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-in-loop-test + (async done + (try + (let [f (^:async fn [] (let [x + ;; force loop in expr position + (loop [xs (map #(js/Promise.resolve %) [1 2 3]) + ys []] + (if (seq xs) + (let [x (first xs) + v (await x)] + (recur (rest xs) (conj ys v))) + ys))] + (identity x))) + v (await (f))] + (is (= [1 2 3] v))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-in-nested + (async done + (try + (let [f (^:async fn [] + (let [b1 1 + b2 (let [x 2] + (+ x + ;; outer let doesn't have awaits + ;; but inner let does, so outer let should become async + (let [x (await (js/Promise.resolve 1))] x))) + b3 (case :foo :foo (case :foo :foo (await (js/Promise.resolve 1)))) + b4 (int ;; wrapped in int to avoid false positive warning: + ;; all arguments must be numbers, got [number + ;; ignore] instead + (try (throw (throw (await (js/Promise.resolve 1)))) (catch :default _ 1 ))) + a (atom 0) + b5 (do (swap! a inc) (swap! a inc) + ;; do with single expr, wrapped in identity to avoid merging with upper do + (identity (do (swap! a (await (js/Promise.resolve inc))))) + ;; do with multiple exprs, wrapped identity to avoid merging with upper do + (identity (do (swap! a inc) (swap! a (await (js/Promise.resolve inc))))) + @a) + b6 (try (identity (try 1 (finally (await nil)))) + (finally nil)) + b7 (letfn [(f [x] x)] + (f (letfn [(f [x] x)] + (f (await 1)))))] + (await (+ b1 b2 b3 b4 b5 b6 b7))))] + (is (= 13 (await (f))))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-with-aliases-or-renamed-and-via-macros-test + (async done + (try + (let [a (await! (js/Promise.resolve 1)) + b (macros/await! (js/Promise.resolve 1)) + c (cc/await (js/Promise.resolve 1)) + d (aw (js/Promise.resolve 1)) + e (cljs.core/await (js/Promise.resolve 1)) + f (clojure.core/await (js/Promise.resolve 1))] + (is (= 1 a)) + (is (= 1 b)) + (is (= 1 c)) + (is (= 1 d)) + (is (= 1 e)) + (is (= 1 f)) + (done)) + (catch :default _ (is false))))) diff --git a/src/test/cljs/cljs/macro_test/macros.clj b/src/test/cljs/cljs/macro_test/macros.clj index 1354e8c01..d57250362 100644 --- a/src/test/cljs/cljs/macro_test/macros.clj +++ b/src/test/cljs/cljs/macro_test/macros.clj @@ -14,3 +14,7 @@ (defmacro sm-cljs-3027 [] (sorted-map "a" "b")) + +(defmacro await! [x] + ;; resolves as clojure.core/await, not cljs.core/await + `(await ~x)) diff --git a/src/test/cljs/test_runner.cljs b/src/test/cljs/test_runner.cljs index 666e0ea82..0488aa477 100644 --- a/src/test/cljs/test_runner.cljs +++ b/src/test/cljs/test_runner.cljs @@ -7,7 +7,8 @@ ;; You must not remove this notice, or any other, from this software. (ns test-runner - (:require [cljs.qualified-method-test] + (:require [cljs.async-await-test] + [cljs.qualified-method-test] [cljs.proxy-test] [cljs.test :refer-macros [run-tests]] [cljs.apply-test] @@ -73,6 +74,7 @@ (enable-console-print!)) (run-tests + 'cljs.async-await-test 'cljs.qualified-method-test 'cljs.proxy-test 'cljs.apply-test From 106991f96b037b0655b6051b30662714b01fe647 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 8 Mar 2026 11:55:39 -0400 Subject: [PATCH 94/94] Add additional async/await tests: literals, new, destructure :or --- src/test/cljs/cljs/async_await_test.cljs | 53 ++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/test/cljs/cljs/async_await_test.cljs b/src/test/cljs/cljs/async_await_test.cljs index 75ffb6758..c3d9d447a 100644 --- a/src/test/cljs/cljs/async_await_test.cljs +++ b/src/test/cljs/cljs/async_await_test.cljs @@ -1,6 +1,8 @@ (ns cljs.async-await-test + (:refer-global :only [Date Promise]) (:require [clojure.test :refer [deftest is async]] - [cljs.core :as cc :refer [await] :rename {await aw}]) + [cljs.core :as cc :refer [await] :rename {await aw}] + [goog.object :as gobj]) (:require-macros [cljs.macro-test.macros :refer [await!] :as macros])) (defn ^:async foo [n] @@ -192,9 +194,9 @@ (let [x (await (js/Promise.resolve 1))] x))) b3 (case :foo :foo (case :foo :foo (await (js/Promise.resolve 1)))) b4 (int ;; wrapped in int to avoid false positive warning: - ;; all arguments must be numbers, got [number - ;; ignore] instead - (try (throw (throw (await (js/Promise.resolve 1)))) (catch :default _ 1 ))) + ;; all arguments must be numbers, got [number + ;; ignore] instead + (try (throw (throw (await (js/Promise.resolve 1)))) (catch :default _ 1 ))) a (atom 0) b5 (do (swap! a inc) (swap! a inc) ;; do with single expr, wrapped in identity to avoid merging with upper do @@ -229,3 +231,46 @@ (is (= 1 f)) (done)) (catch :default _ (is false))))) + +(deftest await-with-ctor + (async done + (let [f (^:async fn [] (Date. (await (Promise/resolve 0))))] + (is (= (Date. 0) (await (f))))) + (done))) + +(deftest await-with-literals + (async done + (let [objf (^:async fn [] #js {:foo (await (Promise/resolve "bar"))})] + (is (gobj/equals #js {:foo "bar"} (await (objf))))) + (let [arrayf (^:async fn [] #js [0 (await (Promise/resolve 1 )) 2])] + (is (= [0 1 2] (seq (await (arrayf)))))) + (let [listf (^:async fn [] (list 0 1 2))] + (is (= '(0 1 2) (await (listf))))) + (let [vectorf (^:async fn [] [0 (await (Promise/resolve 1 )) 2])] + (is (= [0 1 2] (await (vectorf))))) + (let [mapf (^:async fn [] {:foo (await (Promise/resolve :bar))})] + (is (= {:foo :bar} (await (mapf))))) + (let [setf (^:async fn [] #{:foo (await (Promise/resolve :bar)) :baz})] + (is (= #{:foo :bar :baz} (await (setf))))) + (done))) + +(deftest await-with-if-test + (async done + (let [f (^:async fn [] (if (await (Promise/resolve true)) :success :fail))] + (is (= :success (await (f))))) + (done))) + +(defn ^:async async-destructure + [{:keys [foo bar] + :or {bar (await (Promise/resolve "hello!"))}}] + [foo bar]) + +(deftest await-in-async-destructure + (async done + (let [res (await (async-destructure {:foo 1}))] + (is (= [1 "hello!"] res))) + (done))) + +(comment + (clojure.test/run-tests) + )