Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLJS-3425: Bad handling of min / max handling of ##NaN #244

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/main/cljs/cljs/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

Expand Down
19 changes: 14 additions & 5 deletions src/main/clojure/cljs/analyzer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)))
Expand Down
2 changes: 1 addition & 1 deletion src/main/clojure/cljs/compiler.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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]}]
Expand Down
40 changes: 35 additions & 5 deletions src/main/clojure/cljs/externs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -385,15 +411,15 @@
(->
(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)

(->
(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])
Expand All @@ -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
)
32 changes: 31 additions & 1 deletion src/test/clojure/cljs/externs_parsing_tests.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
Expand Down Expand Up @@ -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")))

Expand Down
Loading