diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index dcdf23c31..ef2818486 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -2771,14 +2771,22 @@ reduces them without incurring seq initialization" (defn ^number max "Returns the greatest of the nums." ([x] x) - ([x y] (cljs.core/max x y)) + ([x y] + (cond + (.isNaN js/Number x) x + (.isNaN js/Number y) y + :else (cljs.core/max x 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 + (.isNaN js/Number x) x + (.isNaN js/Number y) y + :else (cljs.core/min x y))) ([x y & more] (reduce min (cljs.core/min x y) more))) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 8c61c4586..ba8ff4240 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3542,6 +3542,12 @@ (list* '. dot-form) " with classification " (classify-dot-form dot-form)))))) +(defn js-global? + "Return true if the expr is a JS global" + [expr] + (and (= 'js (:ns expr)) + (some? (get-in (get-externs) [(-> (:name expr) name symbol)])))) + (defn analyze-dot [env target field member+ form] (let [v [target field member+] {:keys [dot-action target method field args]} (build-dot-form v) @@ -3550,11 +3556,14 @@ form-meta (meta form) target-tag (:tag targetexpr) prop (or field method) - tag (or (:tag form-meta) - (and (js-tag? target-tag) - (vary-meta (normalize-js-tag target-tag) - update-in [:prefix] (fnil conj '[Object]) prop)) - nil)] + tag (if (js-global? targetexpr) + ;; we have a known global, don't treat as instance of some type + (with-meta 'js {:prefix [(-> targetexpr :name name symbol) prop]}) + (or (:tag form-meta) + (and (js-tag? target-tag) + (vary-meta (normalize-js-tag target-tag) + update-in [:prefix] (fnil conj '[Object]) prop)) + nil))] (when (and (not= 'constructor prop) (not (string/starts-with? (str prop) "cljs$")) (not (-> prop meta :protocol-prop))) 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/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj index d86aa91ef..a6d460eab 100644 --- a/src/main/clojure/cljs/externs.clj +++ b/src/main/clojure/cljs/externs.clj @@ -124,6 +124,8 @@ (when (> (.getChildCount node) 0) (parse-extern-node (.getFirstChild node)))) +;; Handle: +;; Math.hypot = function(...) {...}; (defmethod parse-extern-node Token/ASSIGN [^Node node] (when (> (.getChildCount node) 0) (let [ty (get-var-info node) @@ -253,7 +255,20 @@ externs (index-externs (parse-externs externs-file)))) defaults sources)))) -(def externs-map (memoize externs-map*)) +(def ^{:doc "Returns a map of externs in the form: + + {foo {bar {baz {} + woz {...}} + + JavaScript var information is not held in the map itself, but on the + symbols in the map. See the helper `info` for grabbing the metadata. + The metadata map matches the layout of var info of the ClojureScript + analyzer: :file & :line, method info, :ret-tag, :tag, :doc, etc. + are all available. + + See also `parse-externs`."} + externs-map + (memoize externs-map*)) (defn ns-match? [ns-segs var-segs] (or @@ -313,6 +328,18 @@ (parse-externs (resource->source-file rsrc)) (:module desc))})))) +(defn info + "Helper for grabbing var info from an externs map. + Example: + (info externs '[Number isNaN]) + + See `externs-map`" + [externs props] + (-> externs + (get-in (butlast props)) + (find (last props)) + first meta)) + (comment (require '[clojure.java.io :as io] '[cljs.closure :as closure] @@ -354,8 +381,7 @@ [(closure/js-source-file "goog/date/date.js" (io/input-stream (io/resource "goog/date/date.js")))] {}) - (get-in '[goog date month]) - ) + (get-in '[goog date month])) (pprint (analyze-goog-file "goog/date/date.js" 'goog.date.month)) @@ -385,7 +411,7 @@ (-> (filter (fn [s] - (= "externs.zip//webkit_dom.js" (.getName s))) + (= "externs.zip//whatwg_console.js" (.getName s))) (default-externs)) first parse-externs index-externs (find 'console) first meta) @@ -393,7 +419,7 @@ (-> (filter (fn [s] - (= "externs.zip//webkit_dom.js" (.getName s))) + (= "externs.zip//whatwg_console.js" (.getName s))) (default-externs)) first parse-externs index-externs (get-in '[Console prototype]) @@ -402,8 +428,12 @@ (require '[clojure.java.io :as io] '[cljs.closure :as cc]) + ;; react.ext.js needs to be available (-> (cc/js-source-file nil (io/file "react.ext.js")) parse-externs index-externs (get 'React) (find 'Component) first meta) + + (info (externs-map) '[Number]) + (-> (info (externs-map) '[Number isNaN]) :ret-tag) ;; => boolean ) diff --git a/src/test/clojure/cljs/externs_parsing_tests.clj b/src/test/clojure/cljs/externs_parsing_tests.clj index effad773d..1d5a51698 100644 --- a/src/test/clojure/cljs/externs_parsing_tests.clj +++ b/src/test/clojure/cljs/externs_parsing_tests.clj @@ -7,7 +7,10 @@ ;; You must not remove this notice, or any other, from this software. (ns cljs.externs-parsing-tests - (:require [cljs.closure :as closure] + (:require [cljs.analyzer :as ana] + [cljs.closure :as closure] + [cljs.compiler :as comp] + [cljs.env :as env] [cljs.externs :as externs] [clojure.java.io :as io] [clojure.test :as test :refer [deftest is]]) @@ -45,8 +48,35 @@ (find 'HTMLDocument) first meta)] (is (= 'Document (:super info))))) +(deftest test-number-infer-test + (let [cenv (env/default-compiler-env) + aenv (ana/empty-env)] + (is (= (env/with-compiler-env cenv + (:tag (ana/analyze aenv '(.isNaN js/Number 1)))) + 'js/Boolean)))) + +;; TODO: js/subtle.crypto + (comment + (externs/info + (::ana/externs @(env/default-compiler-env)) + '[Number]) + + (externs/info + (::ana/externs @(env/default-compiler-env)) + '[Number isNaN]) + + ;; js/Boolean + (env/with-compiler-env (env/default-compiler-env) + (ana/js-tag '[Number isNaN] :ret-tag)) + + ;; js + (let [cenv (env/default-compiler-env) + aenv (ana/empty-env)] + (->> (env/with-compiler-env cenv + (:tag (ana/analyze aenv '(.isNaN js/Number 1)))))) + (externs/parse-externs (externs/resource->source-file (io/resource "goog/object/object.js")))