From 4b84a1a493f973ed82b9482b492f087877cc2709 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Thu, 20 Mar 2025 14:47:26 -0400 Subject: [PATCH 01/29] * fix up tests so they don't throw if no warnings --- src/test/clojure/cljs/externs_infer_tests.clj | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 8ca7ff9aa..0c6459446 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -158,9 +158,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 +172,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 +185,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-unknown-method-from-externs (let [ws (atom []) @@ -197,9 +197,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 +211,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 +227,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 []) From fd783d6e563d32b7b04c303f91a32e5c8a8ec117 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Thu, 20 Mar 2025 15:28:23 -0400 Subject: [PATCH 02/29] * (wip) externs-var-info, which could be used by both has-extern? and js-tag * test are failing because - we have a bad hack that assumes we have an instance - extern-var-info doesn't tell us what the *resolved* prefix is --- src/main/clojure/cljs/analyzer.cljc | 29 +++++++++++-------- src/test/clojure/cljs/externs_infer_tests.clj | 25 ++++++++++++++++ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 8c61c4586..47f451967 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1030,7 +1030,7 @@ boolean Boolean symbol Symbol}) -(defn has-extern?* +(defn extern-var-info ([pre externs] (let [pre (if-some [me (find (get-in externs '[Window prototype]) @@ -1039,25 +1039,29 @@ (into [tag 'prototype] (next pre)) pre) pre)] - (has-extern?* pre externs externs))) - ([pre externs top] + (extern-var-info pre externs externs nil))) + ([pre externs top info] (cond - (empty? pre) true + (empty? pre) info :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) + info' (meta x')] + (if (and (= 'Function (:tag info')) (:ctor info')) + (or (extern-var-info (into '[prototype] (next pre)) externs' top nil) + (extern-var-info (next pre) externs' top info') ;; check base type if it exists - (when-let [super (:super xmeta)] - (has-extern?* (into [super] (next pre)) externs top))) - (recur (next pre) externs' top)))))))) + (when-let [super (:super info')] + (extern-var-info (into [super] (next pre)) externs top nil))) + (recur (next pre) externs' top info')))))))) + +(defn has-extern?* + [pre externs] + (boolean (extern-var-info pre externs))) (defn has-extern? ([pre] @@ -3569,6 +3573,7 @@ {:warn-type :target :form form :property prop})) ;; Unresolveable property on existing extern (let [[pre' pre] ((juxt butlast identity) (-> tag meta :prefix))] + (println ">>>>>" pre' pre) (when (and (has-extern? pre') (not (has-extern? pre))) (warning :infer-warning env {:warn-type :property :form form diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 0c6459446..3c4db5dfe 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -35,6 +35,25 @@ (is (true? (ana/has-extern? '[baz] externs))) (is (false? (ana/has-extern? '[Baz] externs))))) +(comment + + (def externs + (externs/externs-map + (closure/load-externs + {:externs ["src/test/externs/test.js"] + :use-only-custom-externs true}))) + + ;; working + (externs/info externs '[baz]) + (externs/info externs '[Foo gozMethod]) + + (ana/extern-var-info '[baz] externs) + (ana/extern-var-info '[Foo gozMethod] externs) + + (ana/has-extern? '[Foo] externs) + + ) + (deftest test-has-extern?-defaults (let [externs (externs/externs-map)] (is (true? (ana/has-extern? '[console] externs))) @@ -189,6 +208,12 @@ (string/starts-with? "Cannot resolve property gozMethod for inferred type js/Foo"))))) +(comment + + (clojure.test/test-vars [#'test-type-hint-infer-unknown-method]) + + ) + (deftest test-infer-unknown-method-from-externs (let [ws (atom []) res (infer-test-helper From 5e31004b0f653175d55fff95e6e9f75efb271cf7 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Thu, 20 Mar 2025 16:35:41 -0400 Subject: [PATCH 03/29] * remove println --- src/main/clojure/cljs/analyzer.cljc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 47f451967..70dfed8da 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3573,7 +3573,6 @@ {:warn-type :target :form form :property prop})) ;; Unresolveable property on existing extern (let [[pre' pre] ((juxt butlast identity) (-> tag meta :prefix))] - (println ">>>>>" pre' pre) (when (and (has-extern? pre') (not (has-extern? pre))) (warning :infer-warning env {:warn-type :property :form form From 82c3e07d84eb832abd5f189bc2b995370e8dac8b Mon Sep 17 00:00:00 2001 From: David Nolen Date: Thu, 3 Apr 2025 11:57:48 -0400 Subject: [PATCH 04/29] * wip --- src/main/clojure/cljs/analyzer.cljc | 40 +++++++++++++------ src/test/clojure/cljs/externs_infer_tests.clj | 18 +++++---- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 70dfed8da..66ecbc47f 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -982,8 +982,7 @@ (if-not (= 'js x) (with-meta 'js {:prefix (conj (->> (string/split (name x) #"\.") - (map symbol) vec) - 'prototype)}) + (map symbol) vec))}) x)) (defn ->type-set @@ -1030,7 +1029,9 @@ boolean Boolean symbol Symbol}) -(defn extern-var-info +(defn resolve-extern + "Given a foreign js property list, return a resolved js property list and the + extern var info" ([pre externs] (let [pre (if-some [me (find (get-in externs '[Window prototype]) @@ -1039,10 +1040,10 @@ (into [tag 'prototype] (next pre)) pre) pre)] - (extern-var-info pre externs externs nil))) - ([pre externs top info] + (resolve-extern pre externs externs {:resolved [] :info nil}))) + ([pre externs top ret] (cond - (empty? pre) info + (empty? pre) ret :else (let [x (first pre) me (find externs x)] @@ -1052,16 +1053,31 @@ (let [[x' externs'] me info' (meta x')] (if (and (= 'Function (:tag info')) (:ctor info')) - (or (extern-var-info (into '[prototype] (next pre)) externs' top nil) - (extern-var-info (next pre) externs' top info') - ;; check base type if it exists + (or + ;; first look for a property on the prototype + (resolve-extern (into '[prototype] (next pre)) externs' top + (-> ret + (update :resolved conj 'prototype) + (assoc :info nil))) + ;; then check for "static" property + (resolve-extern (next pre) externs' top + (-> ret + (update :resolved conj x) + (assoc :info info'))) + ;; finally check the super class if there is one (when-let [super (:super info')] - (extern-var-info (into [super] (next pre)) externs top nil))) - (recur (next pre) externs' top info')))))))) + (resolve-extern (into [super] (next pre)) externs top + (-> ret + (update :resolved conj x) + (assoc :info nil))))) + (recur (next pre) externs' top + (-> ret + (update :resolved conj x) + (assoc :info info')))))))))) (defn has-extern?* [pre externs] - (boolean (extern-var-info pre externs))) + (boolean (resolve-extern pre externs))) (defn has-extern? ([pre] diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 3c4db5dfe..ff2989774 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -23,6 +23,15 @@ "goog.isArrayLike;" "Java.type;" "Object.out;" "Object.out.println;" "Object.error;" "Object.error.println;"]) +(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 @@ -37,19 +46,12 @@ (comment - (def externs - (externs/externs-map - (closure/load-externs - {:externs ["src/test/externs/test.js"] - :use-only-custom-externs true}))) + (clojure.test/test-vars [#'test-resolve-extern]) ;; working (externs/info externs '[baz]) (externs/info externs '[Foo gozMethod]) - (ana/extern-var-info '[baz] externs) - (ana/extern-var-info '[Foo gozMethod] externs) - (ana/has-extern? '[Foo] externs) ) From ca7b365029509ebf79658308f7f8aac70f4dcd2b Mon Sep 17 00:00:00 2001 From: davidnolen Date: Mon, 7 Apr 2025 21:25:42 -0400 Subject: [PATCH 05/29] - next problem, resolving console.log w/o the old bits --- src/main/clojure/cljs/analyzer.cljc | 23 ++++++++----------- src/test/clojure/cljs/externs_infer_tests.clj | 17 ++++++++++---- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 66ecbc47f..ec73d8c19 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1033,14 +1033,7 @@ "Given a foreign js property list, return a resolved js property list and the extern var info" ([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)] - (resolve-extern pre externs externs {:resolved [] :info nil}))) + (resolve-extern pre externs externs {:resolved [] :info nil})) ([pre externs top ret] (cond (empty? pre) ret @@ -1054,16 +1047,18 @@ info' (meta x')] (if (and (= 'Function (:tag info')) (:ctor info')) (or + ;; then check for "static" property + (resolve-extern (next pre) externs' top + (-> ret + (update :resolved conj x) + (assoc :info info'))) + ;; first look for a property on the prototype (resolve-extern (into '[prototype] (next pre)) externs' top - (-> ret - (update :resolved conj 'prototype) - (assoc :info nil))) - ;; then check for "static" property - (resolve-extern (next pre) externs' top (-> ret (update :resolved conj x) - (assoc :info info'))) + (assoc :info nil))) + ;; finally check the super class if there is one (when-let [super (:super info')] (resolve-extern (into [super] (next pre)) externs top diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index ff2989774..4b4f4013f 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -44,15 +44,22 @@ (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))))) + (comment - (clojure.test/test-vars [#'test-resolve-extern]) + (def externs (externs/externs-map)) - ;; working - (externs/info externs '[baz]) - (externs/info externs '[Foo gozMethod]) + ;; succeeds + (ana/resolve-extern '[console] externs) - (ana/has-extern? '[Foo] externs) + ;; this one fails + (ana/resolve-extern '[console log] externs) ) From 8ab53af298b29c26608b8edb4d0714ad60448513 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 30 Jun 2025 15:15:20 -0400 Subject: [PATCH 06/29] * handle case where we find an extern and it has a type, use the type to continue resolving * can infer console.log again --- src/main/clojure/cljs/analyzer.cljc | 24 ++++++++++++++----- src/test/clojure/cljs/externs_infer_tests.clj | 10 ++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 4b0a5f408..91acac3ff 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1045,7 +1045,7 @@ :else (let [[x' externs'] me info' (meta x')] - (if (and (= 'Function (:tag info')) (:ctor info')) + (if (and (:ctor info') (= 'Function (:tag info'))) (or ;; then check for "static" property (resolve-extern (next pre) externs' top @@ -1063,12 +1063,24 @@ (when-let [super (:super info')] (resolve-extern (into [super] (next pre)) externs top (-> ret - (update :resolved conj x) + (assoc :resolved []) (assoc :info nil))))) - (recur (next pre) externs' top - (-> ret - (update :resolved conj x) - (assoc :info info')))))))))) + + (or + ;; If the tag isn't Function, try to resolve it + ;; similar to the super case above + (let [tag (:tag info')] + (when (and tag (not= 'Function tag)) + (resolve-extern (into [tag] (next pre)) externs top + (-> ret + (assoc :resolved []) + (assoc :info nil))))) + + ;; assume static property + (recur (next pre) externs' top + (-> ret + (update :resolved conj x) + (assoc :info info'))))))))))) (defn has-extern?* [pre externs] diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 4b4f4013f..1817a0a29 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -49,16 +49,18 @@ (is (= '[Number] (-> (ana/resolve-extern '[Number] externs) :resolved))) (is (= '[Number prototype valueOf] - (-> (ana/resolve-extern '[Number valueOf] externs) :resolved))))) + (-> (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))))) (comment + (clojure.test/test-vars [#'test-resolve-extern]) (def externs (externs/externs-map)) - ;; succeeds (ana/resolve-extern '[console] externs) - - ;; this one fails (ana/resolve-extern '[console log] externs) ) From 08335c64db0f216a3df486f103431edcdd2ba8f5 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Mon, 30 Jun 2025 15:29:32 -0400 Subject: [PATCH 07/29] * undefined is a ref cycle, special case --- src/main/clojure/cljs/analyzer.cljc | 6 +++--- src/test/clojure/cljs/externs_infer_tests.clj | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 91acac3ff..96397c892 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1067,10 +1067,10 @@ (assoc :info nil))))) (or - ;; If the tag isn't Function, try to resolve it - ;; similar to the super case above + ;; If the tag isn't Function or undefined, + ;; try to resolve it similar to the super case above (let [tag (:tag info')] - (when (and tag (not= 'Function tag)) + (when (and tag (not (contains? '#{Function undefined} tag))) (resolve-extern (into [tag] (next pre)) externs top (-> ret (assoc :resolved []) diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 1817a0a29..cacfcc954 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -53,7 +53,9 @@ (is (= '[Console] (-> (ana/resolve-extern '[console] externs) :resolved))) (is (= '[Console prototype log] - (-> (ana/resolve-extern '[console log] externs) :resolved))))) + (-> (ana/resolve-extern '[console log] externs) :resolved))) + (is (= '[undefined] + (-> (ana/resolve-extern '[undefined] externs) :resolved))))) (comment (clojure.test/test-vars [#'test-resolve-extern]) @@ -62,6 +64,7 @@ ;; succeeds (ana/resolve-extern '[console] externs) (ana/resolve-extern '[console log] externs) + (ana/resolve-extern '[undefined] externs) ) From 8b96e4d593ba13d9a0cfab4039ee5cd121bc21ef Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 4 Jul 2025 09:13:56 -0400 Subject: [PATCH 08/29] * remove hacks for global props and Number --- src/main/clojure/cljs/analyzer.cljc | 4 ---- src/test/clojure/cljs/externs_infer_tests.clj | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 96397c892..3a7b44597 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1091,10 +1091,6 @@ (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 js-tag diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index cacfcc954..1ab88ba43 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -65,6 +65,8 @@ (ana/resolve-extern '[console] externs) (ana/resolve-extern '[console log] externs) (ana/resolve-extern '[undefined] externs) + (ana/resolve-extern '[Number isNaN] externs) + (ana/resolve-extern '[document] externs) ) From 1684a811a74e629073d316d09e67f86fab76354b Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 4 Jul 2025 12:50:11 -0400 Subject: [PATCH 09/29] * fix resolve-extern behavior for resolving the var info - we only care about var info for the last property - we don't what prefix resolution to clobber the actual instance var info * remove js-tag, incomplete, instead use resolve-extern --- src/main/clojure/cljs/analyzer.cljc | 56 +++++++++---------- src/test/clojure/cljs/externs_infer_tests.clj | 7 +++ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 3a7b44597..6d5eb5e13 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1033,7 +1033,7 @@ "Given a foreign js property list, return a resolved js property list and the extern var info" ([pre externs] - (resolve-extern pre externs externs {:resolved [] :info nil})) + (resolve-extern pre externs externs {:resolved []})) ([pre externs top ret] (cond (empty? pre) ret @@ -1044,27 +1044,31 @@ (not me) nil :else (let [[x' externs'] me - info' (meta x')] + 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'))] (if (and (:ctor info') (= 'Function (:tag info'))) (or ;; then check for "static" property (resolve-extern (next pre) externs' top - (-> ret - (update :resolved conj x) - (assoc :info info'))) - - ;; first look for a property on the prototype - (resolve-extern (into '[prototype] (next pre)) externs' top - (-> ret - (update :resolved conj x) - (assoc :info nil))) - - ;; finally check the super class if there is one - (when-let [super (:super info')] - (resolve-extern (into [super] (next pre)) externs top - (-> ret - (assoc :resolved []) - (assoc :info nil))))) + (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 isn't Function or undefined, @@ -1072,15 +1076,11 @@ (let [tag (:tag info')] (when (and tag (not (contains? '#{Function undefined} tag))) (resolve-extern (into [tag] (next pre)) externs top - (-> ret - (assoc :resolved []) - (assoc :info nil))))) + (assoc ret :resolved [])))) ;; assume static property (recur (next pre) externs' top - (-> ret - (update :resolved conj x) - (assoc :info info'))))))))))) + (update ret :resolved conj x)))))))))) (defn has-extern?* [pre externs] @@ -1101,12 +1101,8 @@ ([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])] + (symbol "js" (str (alias->type tag tag)))))) (defn dotted-symbol? [sym] (let [s (str sym)] diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 1ab88ba43..a71297e1a 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -82,9 +82,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/undefined (ana/js-tag '[console log] :ret-tag externs))) (is (= 'js/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 From c1cd872c2f490771d24402129cd3a464205d1cf3 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 5 Jul 2025 09:14:27 -0400 Subject: [PATCH 10/29] * 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 * all externs infer tests passing again --- src/main/clojure/cljs/analyzer.cljc | 20 ++++++++++--- src/test/clojure/cljs/externs_infer_tests.clj | 29 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 6d5eb5e13..7355cbaa8 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -980,9 +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))}) + (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 @@ -1082,6 +1083,16 @@ (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))) @@ -3600,7 +3611,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/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index a71297e1a..5db11f14b 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -23,6 +23,26 @@ "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 @@ -233,6 +253,15 @@ (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]) ) From f1a1f10813fc99ddc0cbe070bf509f5982ed6a71 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 5 Jul 2025 09:59:05 -0400 Subject: [PATCH 11/29] * can finally resolve crypto.subtle --- src/main/clojure/cljs/analyzer.cljc | 12 ++++++++---- src/main/clojure/cljs/externs.clj | 17 ++++++++++++++--- src/test/clojure/cljs/externs_infer_tests.clj | 4 +++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 7355cbaa8..4b97855d8 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1056,7 +1056,8 @@ (and (empty? (next pre)) (not (contains? ret :info))) (assoc :info info'))] - (if (and (:ctor info') (= 'Function (:tag 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 @@ -1072,11 +1073,14 @@ (assoc ret :resolved [])))) (or - ;; If the tag isn't Function or undefined, - ;; try to resolve it similar to the super case above + ;; 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))) - (resolve-extern (into [tag] (next pre)) externs top + ;; 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 diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj index c5343e1b1..da5fdcd56 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 (some-> (:value texpr) 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/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 5db11f14b..72246b250 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -75,7 +75,9 @@ (is (= '[Console prototype log] (-> (ana/resolve-extern '[console log] externs) :resolved))) (is (= '[undefined] - (-> (ana/resolve-extern '[undefined] externs) :resolved))))) + (-> (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]) From ed1aec6dbf7b8b585d42ea0ba8c273894b18dd68 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 5 Jul 2025 11:00:59 -0400 Subject: [PATCH 12/29] * resolve-externs is generally useful, add single arity --- src/main/clojure/cljs/analyzer.cljc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 4b97855d8..e531e306c 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1033,6 +1033,8 @@ (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] (resolve-extern pre externs externs {:resolved []})) ([pre externs top ret] From 16ec71197d5d0b64cc27958eeb07386f4b746bb8 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 5 Jul 2025 12:33:26 -0400 Subject: [PATCH 13/29] * add lift-tag-to-js helper - CLJS uses boolean - but externs inference returns js/Boolean * 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 tag instead, Function is not desirable * safe-test? needs to check js/Boolean now * unit tests --- src/main/clojure/cljs/analyzer.cljc | 19 +++++++++++++------ src/main/clojure/cljs/compiler.cljc | 2 +- src/test/clojure/cljs/externs_infer_tests.clj | 13 +++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index e531e306c..15bf472c9 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1110,6 +1110,9 @@ (or (has-extern?* pre externs) (-> (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)) @@ -1119,7 +1122,7 @@ (js-tag pre tag-type externs externs)) ([pre tag-type externs top] (when-let [tag (get-in (resolve-extern pre externs) [:info tag-type])] - (symbol "js" (str (alias->type tag tag)))))) + (lift-tag-to-js tag)))) (defn dotted-symbol? [sym] (let [s (str sym)] @@ -1310,8 +1313,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 @@ -1320,10 +1324,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)] @@ -3585,7 +3591,8 @@ 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) $)) prop (or field method) tag (or (:tag form-meta) (and (js-tag? target-tag) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index b96c09b36..632cc75de 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -641,7 +641,7 @@ (defn safe-test? [env e] (let [tag (ana/infer-tag env e)] - (or (#{'boolean 'seq} tag) (truthy-constant? e)))) + (or ('#{boolean seq js/Boolean} tag) (truthy-constant? e)))) (defmethod emit* :if [{:keys [test then else env unchecked]}] diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 72246b250..64feac7f3 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -87,6 +87,7 @@ (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) @@ -146,6 +147,18 @@ (map (comp :externs second) (get @test-cenv ::ana/namespaces)))))))))))) +(deftest test-externs-type-infer + (is (= 'js/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 (= 'js/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)))) + (deftest test-externs-infer (is (= 'js/Foo (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] From 561d65a5797d04583d18cfca62a09b9ad0a5c681 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 5 Jul 2025 13:10:49 -0400 Subject: [PATCH 14/29] * test assertion that we can figure out the return even if the ctor is bound to a local --- src/test/clojure/cljs/externs_infer_tests.clj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 64feac7f3..8356aa222 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -157,6 +157,12 @@ (-> (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 (= 'js/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)))) (deftest test-externs-infer From 0285a470000cbfa465249a55155df2fb27fd90c5 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 5 Jul 2025 13:19:52 -0400 Subject: [PATCH 15/29] * cleanup safe-test? * show that type inference for a crypto.subtle call works --- src/main/clojure/cljs/compiler.cljc | 3 ++- src/test/clojure/cljs/externs_infer_tests.clj | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index 632cc75de..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 js/Boolean} 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/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 8356aa222..ddd3cd2a1 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -163,6 +163,11 @@ (env/with-compiler-env (env/default-compiler-env) (analyze (ana/empty-env) '(let [x js/Number] (.isNaN x 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)))) (deftest test-externs-infer From 3852e6749864816880900fcf0482396a421349eb Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 5 Jul 2025 13:24:55 -0400 Subject: [PATCH 16/29] * add compiler test case for inferring return of Number.isNaN --- src/test/clojure/cljs/compiler_tests.clj | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test/clojure/cljs/compiler_tests.clj b/src/test/clojure/cljs/compiler_tests.clj index bb6a9bfc3..893b0b504 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,13 @@ window))]))] (is (re-find #"window__\$1" code))))) +(deftest test-externs-infer-is-nan + (testing "Let binding which use JS global names should get shadowed" + (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)))))) + ;; CLJS-1225 (comment From 5da21cc082b4738400d4e1f4a048ceefa2ee6c05 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 5 Jul 2025 13:55:05 -0400 Subject: [PATCH 17/29] * add non-ctor inference for array, string, boolean and number, will be useful later --- src/main/clojure/cljs/analyzer.cljc | 23 ++++++++++++++----- src/test/clojure/cljs/externs_infer_tests.clj | 5 ++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 15bf472c9..b1e71c065 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -2627,12 +2627,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" @@ -3585,6 +3585,16 @@ (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) @@ -3592,7 +3602,8 @@ targetexpr (analyze enve target) form-meta (meta form) target-tag (as-> (:tag targetexpr) $ - (or (some-> $ meta :ctor lift-tag-to-js) $)) + (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) diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index ddd3cd2a1..40034e382 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -168,6 +168,11 @@ (-> (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 (= 'js/String + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.toUpperCase "foo")))) :tag)))) (deftest test-externs-infer From 31e04f474eea1bf416d3809d2a739739b9cf38aa Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 06:55:53 -0400 Subject: [PATCH 18/29] * fix test string --- src/test/clojure/cljs/compiler_tests.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/clojure/cljs/compiler_tests.clj b/src/test/clojure/cljs/compiler_tests.clj index 893b0b504..62530482b 100644 --- a/src/test/clojure/cljs/compiler_tests.clj +++ b/src/test/clojure/cljs/compiler_tests.clj @@ -376,7 +376,7 @@ (is (re-find #"window__\$1" code))))) (deftest test-externs-infer-is-nan - (testing "Let binding which use JS global names should get shadowed" + (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)]))] From 7ca2386d79c2d444f8eacfb062e83d4614876bf3 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 06:57:51 -0400 Subject: [PATCH 19/29] * don't need some-> --- 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 da5fdcd56..d25987cde 100644 --- a/src/main/clojure/cljs/externs.clj +++ b/src/main/clojure/cljs/externs.clj @@ -74,7 +74,7 @@ (defn simplify-texpr [texpr] (case (:type texpr) - :string-lit (some-> (:value texpr) add-prefix) + :string-lit (-> texpr :value add-prefix) :star 'any ;; TODO: qmark should probably be #{nil T} (:qmark :bang) (simplify-texpr (-> texpr :children first)) From fc00df184a890a741194e030445ae83484f36f3c Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 07:01:26 -0400 Subject: [PATCH 20/29] * add js/isNaN test --- src/test/clojure/cljs/externs_infer_tests.clj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 40034e382..cb996fde6 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -164,6 +164,11 @@ (analyze (ana/empty-env) '(let [x js/Number] (.isNaN x 1))))) :tag))) + (is (= 'js/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) From 7c7fca7f234a9620bc465f1cab71822fd3c6b49d Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 07:08:50 -0400 Subject: [PATCH 21/29] * add isArray extern test --- src/test/clojure/cljs/externs_infer_tests.clj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index cb996fde6..b6d3e8495 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -178,6 +178,11 @@ (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] (env/with-compiler-env (env/default-compiler-env) (analyze (ana/empty-env) '(.toUpperCase "foo")))) + :tag))) + (is (= 'js/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)))) (deftest test-externs-infer From dca338e1655d70abf64a9cdece1d7c70a1acf430 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 07:50:14 -0400 Subject: [PATCH 22/29] * don't return raised js/Foo types for boolean, number, string * remove ^boolean hint from array? * fix tests * add test inference test case for array? --- src/main/cljs/cljs/core.cljs | 2 +- src/main/clojure/cljs/analyzer.cljc | 7 ++++++- src/test/clojure/cljs/externs_infer_tests.clj | 14 +++++++------- src/test/clojure/cljs/type_inference_tests.clj | 7 +++++++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index b97f00fa2..b3397d68f 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") diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index b1e71c065..2a6364c8c 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -1122,7 +1122,12 @@ (js-tag pre tag-type externs externs)) ([pre tag-type externs top] (when-let [tag (get-in (resolve-extern pre externs) [:info tag-type])] - (lift-tag-to-js tag)))) + (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)] diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index b6d3e8495..92cf2d85c 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -106,7 +106,7 @@ (is (= 'js/Console (ana/js-tag '[console] :tag externs))) (is (= 'js/Function (ana/js-tag '[console log] :tag externs))) (is (= 'js/undefined (ana/js-tag '[console log] :ret-tag externs))) - (is (= 'js/Boolean (ana/js-tag '[Number isNaN] :ret-tag externs))) + (is (= 'boolean (ana/js-tag '[Number isNaN] :ret-tag externs))) (is (= 'js/Foo (ana/js-tag '[baz] :ret-tag externs))))) (comment @@ -148,23 +148,23 @@ (get @test-cenv ::ana/namespaces)))))))))))) (deftest test-externs-type-infer - (is (= 'js/Boolean + (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 (= 'js/Boolean + (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 (= 'js/Boolean + (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 (= 'js/Boolean + (is (= 'boolean (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] (env/with-compiler-env (env/default-compiler-env) (analyze (ana/empty-env) '(js/isNaN 1)))) @@ -174,12 +174,12 @@ (env/with-compiler-env (env/default-compiler-env) (analyze (ana/empty-env) '(.generateKey js/crypto.subtle)))) :tag))) - (is (= 'js/String + (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 (= 'js/Boolean + (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))))) diff --git a/src/test/clojure/cljs/type_inference_tests.clj b/src/test/clojure/cljs/type_inference_tests.clj index c9c5f6343..9b6066896 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -313,6 +313,13 @@ ; '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))))))))) + (deftest test-always-true-if (is (= (env/with-compiler-env test-cenv (:tag (analyze test-env '(if 1 2 "foo")))) From 191798ae218d06e4450eb0b62b6ef5b9fdec03d7 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 08:03:40 -0400 Subject: [PATCH 23/29] * remove hint for make-array, add test --- src/main/cljs/cljs/core.cljs | 2 +- src/test/clojure/cljs/type_inference_tests.clj | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index b3397d68f..f188d2c0f 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -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 diff --git a/src/test/clojure/cljs/type_inference_tests.clj b/src/test/clojure/cljs/type_inference_tests.clj index 9b6066896..556113613 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -318,7 +318,10 @@ internal implementation uses standard JS APIs" (is (= 'boolean (env/with-compiler-env test-cenv - (:tag (analyze test-env '(array? (array))))))))) + (: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 From 41fc12843758b729ace018cdd643e77f7a67aa4c Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 08:09:51 -0400 Subject: [PATCH 24/29] * remove hints for isFinite and isSafeInteger, tests --- src/main/cljs/cljs/core.cljs | 4 ++-- src/test/clojure/cljs/externs_infer_tests.clj | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index f188d2c0f..9f0d8203c 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -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 diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 92cf2d85c..967164d1f 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -183,6 +183,16 @@ (-> (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 From 8625f7fdd03f49a81723beedc3c54d27cc207158 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 08:29:31 -0400 Subject: [PATCH 25/29] * can infer distinct? - test case * remove some more ^boolean * FIXME comments about dubious ^boolean cases --- src/main/cljs/cljs/core.cljs | 11 +++++++---- src/test/clojure/cljs/type_inference_tests.clj | 7 +++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 9f0d8203c..2c96fb7f9 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -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)))) @@ -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))) diff --git a/src/test/clojure/cljs/type_inference_tests.clj b/src/test/clojure/cljs/type_inference_tests.clj index 556113613..fc41aeca4 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -307,6 +307,13 @@ (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)) + ;; 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)))) From 2619b1de234e38a0227ee0f3c70857f242a95143 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 08:32:43 -0400 Subject: [PATCH 26/29] * move ^boolean hint from special-symbol? to contains? where it belongs - add test case --- src/main/cljs/cljs/core.cljs | 4 ++-- src/test/clojure/cljs/type_inference_tests.clj | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 2c96fb7f9..1d981b562 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -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 @@ -11945,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? diff --git a/src/test/clojure/cljs/type_inference_tests.clj b/src/test/clojure/cljs/type_inference_tests.clj index fc41aeca4..abb99a048 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -310,6 +310,9 @@ (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)))) From 979715c8b645d06c57dfc03d7b800796bc6be123 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 08:45:18 -0400 Subject: [PATCH 27/29] * goog.object/containsKey type inference doesn't work for reason, leave a trail for later --- src/main/cljs/cljs/core.cljs | 2 ++ src/test/clojure/cljs/externs_parsing_tests.clj | 6 ++++++ src/test/clojure/cljs/type_inference_tests.clj | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 1d981b562..ae09ddde3 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12178,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}] 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 abb99a048..efac33a63 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -394,3 +394,15 @@ (:import [goog.history Html5History])) (Html5History.)] {} true)))))) + +;; FIXME: infers any instead of boolean, nothing wrong w/ the externs parsing +;; but this definitely does not work at the moment +#_(deftest test-goog-infer + (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 fc0467f0ea5f8586263ecd8b57c55218fd731789 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 09:09:50 -0400 Subject: [PATCH 28/29] * goog.string/contains does work, add test * remove hint from NaN? * FIXME note about re-matches --- src/main/cljs/cljs/core.cljs | 5 +++-- src/test/clojure/cljs/compiler_tests.clj | 9 +++++++++ src/test/clojure/cljs/type_inference_tests.clj | 14 +++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index ae09ddde3..e1c7b067e 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -12273,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)] @@ -12297,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)) @@ -12326,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/test/clojure/cljs/compiler_tests.clj b/src/test/clojure/cljs/compiler_tests.clj index 62530482b..52b3f14c8 100644 --- a/src/test/clojure/cljs/compiler_tests.clj +++ b/src/test/clojure/cljs/compiler_tests.clj @@ -382,6 +382,15 @@ '[(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/contain "foobar" "foo") 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 efac33a63..5435cc90f 100644 --- a/src/test/clojure/cljs/type_inference_tests.clj +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -395,10 +395,17 @@ (Html5History.)] {} true)))))) -;; FIXME: infers any instead of boolean, nothing wrong w/ the externs parsing -;; but this definitely does not work at the moment -#_(deftest test-goog-infer +(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 @@ -406,3 +413,4 @@ (:require [goog.object :as gobject])) (gobject/containsKey (js-object) "foo")] {} true)))))) + From d656c4cedd5ebdb5f2f38ea3741328b738bfe877 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sun, 6 Jul 2025 09:41:52 -0400 Subject: [PATCH 29/29] * typo in last commit --- src/test/clojure/cljs/compiler_tests.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/clojure/cljs/compiler_tests.clj b/src/test/clojure/cljs/compiler_tests.clj index 52b3f14c8..95204e650 100644 --- a/src/test/clojure/cljs/compiler_tests.clj +++ b/src/test/clojure/cljs/compiler_tests.clj @@ -388,7 +388,7 @@ (compile-form-seq '[(ns test.foo (:require [goog.string :as gstring])) - (if (gstring/contain "foobar" "foo") true false)]))] + (if (gstring/contains "foobar" "foo") true false)]))] (is (nil? (re-find #"truth_" code)))))) ;; CLJS-1225