diff --git a/build.edn b/build.edn index c54f2ec13..a45ab4226 100644 --- a/build.edn +++ b/build.edn @@ -9,8 +9,8 @@ :npm-deps {:lodash "4.17.4"} :closure-warnings {:non-standard-jsdoc :off :global-this :off} :install-deps true - :language-in :es6 - :language-out :es5 + :language-in :ecmascript-2020 + :language-out :ecmascript-2020 :foreign-libs [{:file "src/test/cljs/calculator_global.js" :provides ["calculator"] :global-exports {calculator Calculator}} @@ -22,4 +22,4 @@ :provides ["calculator"]} {:file "src/test/cljs/es6_default_hello.js" :provides ["es6_default_hello"] - :module-type :es6}]} \ No newline at end of file + :module-type :es6}]} diff --git a/resources/test.edn b/resources/test.edn index a8f258139..243058fac 100644 --- a/resources/test.edn +++ b/resources/test.edn @@ -9,7 +9,8 @@ :npm-deps {:lodash "4.17.4"} :closure-warnings {:non-standard-jsdoc :off :global-this :off} :install-deps true - :language-out :es5 + :language-in :ecmascript-2020 + :language-out :ecmascript-2020 :foreign-libs [{:file "src/test/cljs/calculator_global.js" :provides ["calculator"] diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 620bc8abc..f5fb8aa93 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -250,10 +250,20 @@ (.isArray js/Array x) (instance? js/Array x))) +(declare Integer) + (defn ^boolean number? - "Returns true if x is a JavaScript number." + "Returns true if x is a JavaScript Number or BigInt" + [x] + (or (cljs.core/js-number? x) + (cljs.core/bigint? x) + (instance? Integer x))) + +(defn ^boolean bigint? + "Returns true if x is a JavaScript Number or BigInt" [x] - (cljs.core/number? x)) + (or (cljs.core/bigint? x) + (instance? Integer x))) (defn not "Returns true if x is logical false, false otherwise." @@ -341,8 +351,12 @@ (if (and (exists? js/Symbol) (identical? (goog/typeOf js/Symbol) "function")) - (def ITER_SYMBOL (.-iterator js/Symbol)) - (def ITER_SYMBOL "@@iterator")) + (do + (def ITER_SYMBOL (.-iterator js/Symbol)) + (def TO_PRIM_SYMBOL (.-toPrimitive js/Symbol))) + (do + (def ITER_SYMBOL "@@iterator") + (def TO_PRIM_SYMBOL "@@toPrimitive"))) (def ^{:jsdoc ["@enum {string}"]} CHAR_MAP @@ -1011,41 +1025,42 @@ h (add-to-string-hash-cache k))))) +(defn- safe-value? [n] + (and (<= n js/Number.MAX_SAFE_INTEGER) + (>= n js/Number.MIN_SAFE_INTEGER))) + +(declare hash) + +(defn hash-bigint [n] + (if (safe-value? n) + (hash (js/Number. n)) + (hash-string (.toString n 32)))) + +(defn hash-number [n] + (if ^boolean (js/isFinite n) + (js-mod (Math/floor n) 2147483647) + (case n + ##Inf 2146435072 + ##-Inf -1048576 + 2146959360))) + (defn hash "Returns the hash code of its argument. Note this is the hash code consistent with =." [o] (cond - (implements? IHash o) - (bit-xor (-hash o) 0) - - (number? o) - (if ^boolean (js/isFinite o) - (js-mod (Math/floor o) 2147483647) - (case o - ##Inf - 2146435072 - ##-Inf - -1048576 - 2146959360)) - + (implements? IHash o) (bit-xor (-hash o) 0) + (bigint? o) (hash-bigint o) + (number? o) (hash-number o) ;; note: mirrors Clojure's behavior on the JVM, where the hashCode is ;; 1231 for true and 1237 for false ;; http://docs.oracle.com/javase/7/docs/api/java/lang/Boolean.html#hashCode%28%29 (true? o) 1231 - (false? o) 1237 - - (string? o) - (m3-hash-int (hash-string o)) - - (instance? js/Date o) - (bit-xor (.valueOf o) 0) - + (string? o) (m3-hash-int (hash-string o)) + (instance? js/Date o) (bit-xor (.valueOf o) 0) (nil? o) 0 - - :else - (bit-xor (-hash o) 0))) + :else (bit-xor (-hash o) 0))) (defn hash-combine [seed hash] ; a la boost @@ -1084,6 +1099,45 @@ (declare get) +;; wrapper type to simplify bigint integration +;; Integer has two fields, if number is null then beyond the range of +;; JS safe integral values. bigint is set for comparisons. +(deftype Integer [number bigint ^:mutable __hash] + Object + (toString [_] + (.toString bigint)) + (equiv [this other] (-equiv this other)) + + IEquiv + (-equiv [_ other] + (cond + (instance? Integer other) (if (nil? number) + (== bigint (.-bigint other)) + (== number (.-number other))) + (js-number? other) (== number other) + (bigint? other) (== bigint other) + :else false)) + + IHash + (-hash [_] + (if (nil? __hash) + (if (nil? bigint) + (set! __hash (hash-number number)) + (set! __hash (hash-bigint bigint)))) + __hash) + + IPrintWithWriter + (-pr-writer [_ writer _] + (-write writer (or number bigint)) + (-write writer "N"))) + +(unchecked-set (.-prototype Integer) TO_PRIM_SYMBOL + (fn [hint] + (this-as this + (if (nil? (.-number this)) + (.-bigint this) + (.-number this))))) + (deftype Symbol [ns name str ^:mutable _hash _meta] Object (toString [_] str) @@ -1433,7 +1487,19 @@ (extend-type number IEquiv - (-equiv [x o] (identical? x o))) + (-equiv [x o] + (cond + (bigint? o) (coercive-= x o) + (instance? Integer o) (-equiv o x) + :else (identical? x o)))) + +(extend-type bigint + IEquiv + (-equiv [x o] + (cond + (js-number? o) (coercive-= x o) + (instance? Integer o) (-equiv o x) + :else (identical? x o)))) (declare with-meta) @@ -2313,7 +2379,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 ^boolean (js/Number.isNaN n)) (not (identical? n js/Infinity)) (== (js/parseFloat n) (js/parseInt n 10)))) @@ -6686,7 +6752,7 @@ reduces them without incurring seq initialization" ;;; PersistentArrayMap -(defn- array-index-of-nil? [arr] +(defn- array-index-of-nil [arr] (let [len (alength arr)] (loop [i 0] (cond @@ -6694,7 +6760,7 @@ reduces them without incurring seq initialization" (nil? (aget arr i)) i :else (recur (+ i 2)))))) -(defn- array-index-of-keyword? [arr k] +(defn- array-index-of-keyword [arr k] (let [len (alength arr) kstr (.-fqn k)] (loop [i 0] @@ -6704,7 +6770,7 @@ reduces them without incurring seq initialization" (identical? kstr (.-fqn (aget arr i)))) i :else (recur (+ i 2)))))) -(defn- array-index-of-symbol? [arr k] +(defn- array-index-of-symbol [arr k] (let [len (alength arr) kstr (.-str k)] (loop [i 0] @@ -6714,6 +6780,17 @@ reduces them without incurring seq initialization" (identical? kstr (.-str (aget arr i)))) i :else (recur (+ i 2)))))) +(defn- equal-number? [x y] + (and (number? x) (number? y) (-equiv x y))) + +(defn- array-index-of-number [arr k] + (let [len (alength arr)] + (loop [i 0] + (cond + (<= len i) -1 + (equal-number? k (aget arr i)) i + :else (recur (+ i 2)))))) + (defn- array-index-of-identical? [arr k] (let [len (alength arr)] (loop [i 0] @@ -6722,7 +6799,7 @@ reduces them without incurring seq initialization" (identical? k (aget arr i)) i :else (recur (+ i 2)))))) -(defn- array-index-of-equiv? [arr k] +(defn- array-index-of-equiv [arr k] (let [len (alength arr)] (loop [i 0] (cond @@ -6732,17 +6809,20 @@ reduces them without incurring seq initialization" (defn array-index-of [arr k] (cond - (keyword? k) (array-index-of-keyword? arr k) + (keyword? k) (array-index-of-keyword arr k) - (or (string? k) (number? k)) + (string? k) (array-index-of-identical? arr k) - (symbol? k) (array-index-of-symbol? arr k) + (number? k) + (array-index-of-number arr k) + + (symbol? k) (array-index-of-symbol arr k) (nil? k) - (array-index-of-nil? arr) + (array-index-of-nil arr) - :else (array-index-of-equiv? arr k))) + :else (array-index-of-equiv arr k))) (defn- array-map-index-of [m k] (array-index-of (.-arr m) k)) @@ -10509,10 +10589,10 @@ reduces them without incurring seq initialization" (number? obj) (-write writer (cond - ^boolean (js/isNaN obj) "##NaN" + ^boolean (js/Number.isNaN obj) "##NaN" (identical? obj js/Number.POSITIVE_INFINITY) "##Inf" (identical? obj js/Number.NEGATIVE_INFINITY) "##-Inf" - :else (str obj))) + :else (str obj (when (bigint? obj) "N")))) (object? obj) (do @@ -12211,7 +12291,7 @@ reduces them without incurring seq initialization" (defn ^boolean NaN? "Returns true if num is NaN, else false" [val] - (js/isNaN val)) + (js/Number.isNaN val)) (defn ^:private parsing-err "Construct message for parsing for non-string parsing error" diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index b96c09b36..97e95c9a0 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -313,7 +313,11 @@ (defmethod emit-constant* nil [x] (emits "null")) #?(:clj - (defmethod emit-constant* Long [x] (emits "(" x ")"))) + (defmethod emit-constant* Long [x] + (if (or (> x 9007199254740991) + (< x -9007199254740991)) + (emits "new cljs.core.Integer(null," x "n," (hash x) ")") + (emits "(" x ")")))) #?(:clj (defmethod emit-constant* Integer [x] (emits x))) ; reader puts Integers in metadata @@ -345,7 +349,14 @@ (defmethod emit-constant* BigDecimal [x] (emits (.doubleValue ^BigDecimal x)))) #?(:clj - (defmethod emit-constant* clojure.lang.BigInt [x] (emits (.doubleValue ^clojure.lang.BigInt x)))) + (defmethod emit-constant* clojure.lang.BigInt [x] + (if (or (> x 9007199254740991) + (< x -9007199254740991)) + ;; not we don't set hash code at compile time because this is difficult to replicate + (emits "new cljs.core.Integer(null, " (.toString ^clojure.lang.BigInt x) "n, null)") + (emits "new cljs.core.Integer(" + (.toString ^clojure.lang.BigInt x) ", " (.toString ^clojure.lang.BigInt x) + "n, null)")))) (defmethod emit-constant* #?(:clj String :cljs js/String) [x] (emits (wrap-in-double-quotes (escape-string x)))) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 72f465427..d7c3b07d2 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -1007,9 +1007,12 @@ `(let [c# ~c x# ~x] (~'js* "(~{} instanceof ~{})" x# c#))))) -(core/defmacro number? [x] +(core/defmacro js-number? [x] (bool-expr (core/list 'js* "typeof ~{} === 'number'" x))) +(core/defmacro bigint? [x] + (bool-expr (core/list 'js* "typeof ~{} === 'bigint'" x))) + (core/defmacro symbol? [x] (bool-expr `(instance? Symbol ~x))) @@ -1159,7 +1162,7 @@ (core/defmacro ^::ana/numeric == ([x] true) - ([x y] (bool-expr (core/list 'js* "(~{} === ~{})" x y))) + ([x y] (bool-expr (core/list 'js* "(~{} == ~{})" x y))) ([x y & more] `(and (== ~x ~y) (== ~y ~@more)))) (core/defmacro ^::ana/numeric dec [x] diff --git a/src/test/cljs/cljs/bigint_test.cljs b/src/test/cljs/cljs/bigint_test.cljs new file mode 100644 index 000000000..e273e5bdf --- /dev/null +++ b/src/test/cljs/cljs/bigint_test.cljs @@ -0,0 +1,49 @@ +;; 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.bigint-test + (:require [cljs.test :refer-macros [deftest is testing]])) + +(deftest test-bigint + (testing "BigInt Basics" + (is (bigint? 1N)) + (is (bigint? 9007199254740992)) + (is (bigint? -9007199254740992))) + (testing "BigInt & Number equality" + (is (= 1 1N)) + (is (= 1N 1)) + (is (= 1 1N 1)) + (is (== 1 1N)) + (is (== 1N 1)) + (is (== 1 1N 1))) + (testing "BigInt Hashing" + (is (= (hash 1N) (hash 1))) + (is (= (hash 9007199254740992) (hash 9007199254740992))) + (is (= (hash -9007199254740992) (hash -9007199254740992)))) + (testing "BigInt as HashMap keys" + (let [m {1N 2}] + (is (= 2 (get m 1N))) + (is (= 2 (get m 1))))) + (testing "Interop" + (is (= (js/BigInt 1) 1N)) + (is (= 1N (js/BigInt 1))) + (is (= (js/BigInt 1) 1N)) + (is (= (js/BigInt 1) 1)) + (is (= 1 (js/BigInt 1)))) + (testing "Interaction with core" + (is (= (range 1 5) (range 1 5N)))) + (testing "Arithmetic" + (is (= 2 (+ 1 1N))) + (is (= 0.5 (/ 1N 2))) + (is (= 4N (* 2N 2))))) + +(comment + + (cljs.test/run-tests) + + ) diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs index 812d3a74a..471a42943 100644 --- a/src/test/cljs/cljs/core_test.cljs +++ b/src/test/cljs/cljs/core_test.cljs @@ -1040,8 +1040,8 @@ (deftest test-807 (testing "Testing CLJS-807, big int, float, big dec literals" - (is (= -1 -1N)) - (is (= 9.007199254740996E15 9007199254740995N)) + ;(is (= -1 -1N)) ;; invalid, -1N is now JS BigInt + ;(is (= 9.007199254740996E15 9007199254740995N)) ;; invalid, we emit JS BigInt now (is (= 1.5 1.5M)) (is (= 4.9E-324 5E-324M))))