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
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
@@ -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)))

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

Loading
Oops, something went wrong.