Skip to content

Commit 0d58703

Browse files
committed
Merge branch 'master' into adv-source-map-sources
2 parents 5bd422c + aa5e751 commit 0d58703

File tree

10 files changed

+367
-79
lines changed

10 files changed

+367
-79
lines changed

src/main/cljs/cljs/core.cljs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@
243243
[x]
244244
(coercive-= x nil))
245245

246-
(defn ^boolean array?
246+
(defn array?
247247
"Returns true if x is a JavaScript array."
248248
[x]
249249
(if (identical? *target* "nodejs")
@@ -444,7 +444,7 @@
444444

445445
(declare apply)
446446

447-
(defn ^array make-array
447+
(defn make-array
448448
"Construct a JavaScript array of the specified dimensions. Accepts ignored
449449
type argument for compatibility with Clojure. Note that there is no efficient
450450
way to allocate multi-dimensional arrays in JavaScript; as such, this function
@@ -1055,8 +1055,8 @@
10551055
(bit-xor (-hash o) 0)
10561056

10571057
(number? o)
1058-
(if ^boolean (js/isFinite o)
1059-
(if-not ^boolean (.isSafeInteger js/Number o)
1058+
(if (js/isFinite o)
1059+
(if-not (.isSafeInteger js/Number o)
10601060
(hash-double o)
10611061
(js-mod (Math/floor o) 2147483647))
10621062
(case o
@@ -2355,7 +2355,7 @@ reduces them without incurring seq initialization"
23552355
"Returns true if n is a JavaScript number with no decimal part."
23562356
[n]
23572357
(and (number? n)
2358-
(not ^boolean (js/isNaN n))
2358+
(not (js/isNaN n))
23592359
(not (identical? n js/Infinity))
23602360
(== (js/parseFloat n) (js/parseInt n 10))))
23612361

@@ -2432,7 +2432,7 @@ reduces them without incurring seq initialization"
24322432
(or (identical? x js/Number.POSITIVE_INFINITY)
24332433
(identical? x js/Number.NEGATIVE_INFINITY)))
24342434

2435-
(defn contains?
2435+
(defn ^boolean contains?
24362436
"Returns true if key is present in the given collection, otherwise
24372437
returns false. Note that for numerically indexed collections like
24382438
vectors and arrays, this tests if the numeric key is within the
@@ -2462,12 +2462,12 @@ reduces them without incurring seq initialization"
24622462
(contains? coll k))
24632463
(MapEntry. k (get coll k) nil))))
24642464

2465-
(defn ^boolean distinct?
2465+
(defn distinct?
24662466
"Returns true if no two of the arguments are ="
24672467
([x] true)
24682468
([x y] (not (= x y)))
24692469
([x y & more]
2470-
(if (not (= x y))
2470+
(if (not (= x y))
24712471
(loop [s #{x y} xs more]
24722472
(let [x (first xs)
24732473
etc (next xs)]
@@ -8351,6 +8351,7 @@ reduces them without incurring seq initialization"
83518351
(if (identical? node root)
83528352
nil
83538353
(set! root node))
8354+
;; FIXME: can we figure out something better here?
83548355
(if ^boolean (.-val added-leaf?)
83558356
(set! count (inc count)))
83568357
tcoll))
@@ -8372,6 +8373,7 @@ reduces them without incurring seq initialization"
83728373
(if (identical? node root)
83738374
nil
83748375
(set! root node))
8376+
;; FIXME: can we figure out something better here?
83758377
(if ^boolean (.-val removed-leaf?)
83768378
(set! count (dec count)))
83778379
tcoll)))
@@ -10562,6 +10564,7 @@ reduces them without incurring seq initialization"
1056210564
(pr-writer (meta obj) writer opts)
1056310565
(-write writer " "))
1056410566
(cond
10567+
;; FIXME: can we figure out something better here?
1056510568
;; handle CLJS ctors
1056610569
^boolean (.-cljs$lang$type obj)
1056710570
(.cljs$lang$ctorPrWriter obj obj writer opts)
@@ -10576,7 +10579,7 @@ reduces them without incurring seq initialization"
1057610579
(number? obj)
1057710580
(-write writer
1057810581
(cond
10579-
^boolean (js/isNaN obj) "##NaN"
10582+
(js/isNaN obj) "##NaN"
1058010583
(identical? obj js/Number.POSITIVE_INFINITY) "##Inf"
1058110584
(identical? obj js/Number.NEGATIVE_INFINITY) "##-Inf"
1058210585
:else (str_ obj)))
@@ -11942,7 +11945,7 @@ reduces them without incurring seq initialization"
1194211945
(fn [x y]
1194311946
(cond (pred x y) -1 (pred y x) 1 :else 0)))
1194411947

11945-
(defn ^boolean special-symbol?
11948+
(defn special-symbol?
1194611949
"Returns true if x names a special form"
1194711950
[x]
1194811951
(contains?
@@ -12175,6 +12178,8 @@ reduces them without incurring seq initialization"
1217512178
Object
1217612179
(findInternedVar [this sym]
1217712180
(let [k (munge (str_ sym))]
12181+
;; FIXME: this shouldn't need ^boolean due to GCL library analysis,
12182+
;; but not currently working
1217812183
(when ^boolean (gobject/containsKey obj k)
1217912184
(let [var-sym (symbol (str_ name) (str_ sym))
1218012185
var-meta {:ns this}]
@@ -12268,7 +12273,7 @@ reduces them without incurring seq initialization"
1226812273
(when (nil? NS_CACHE)
1226912274
(set! NS_CACHE (atom {})))
1227012275
(let [ns-str (str_ ns)
12271-
ns (if (not ^boolean (gstring/contains ns-str "$macros"))
12276+
ns (if (not (gstring/contains ns-str "$macros"))
1227212277
(symbol (str_ ns-str "$macros"))
1227312278
ns)
1227412279
the-ns (get @NS_CACHE ns)]
@@ -12292,7 +12297,7 @@ reduces them without incurring seq initialization"
1229212297
[x]
1229312298
(instance? goog.Uri x))
1229412299

12295-
(defn ^boolean NaN?
12300+
(defn NaN?
1229612301
"Returns true if num is NaN, else false"
1229712302
[val]
1229812303
(js/isNaN val))
@@ -12321,6 +12326,7 @@ reduces them without incurring seq initialization"
1232112326
[s]
1232212327
(if (string? s)
1232312328
(cond
12329+
;; FIXME: another cases worth thinking about
1232412330
^boolean (re-matches #"[\x00-\x20]*[+-]?NaN[\x00-\x20]*" s) ##NaN
1232512331
^boolean (re-matches
1232612332
#"[\x00-\x20]*[+-]?(Infinity|((\d+\.?\d*|\.\d+)([eE][+-]?\d+)?)[dDfF]?)[\x00-\x20]*"

src/main/clojure/cljs/analyzer.cljc

Lines changed: 108 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -977,13 +977,16 @@
977977
(or (= 'js x)
978978
(= "js" (namespace x)))))
979979

980+
(defn ->pre [x]
981+
(->> (string/split (name x) #"\.") (map symbol)))
982+
980983
(defn normalize-js-tag [x]
981984
;; if not 'js, assume constructor
982985
(if-not (= 'js x)
983-
(with-meta 'js
984-
{:prefix (conj (->> (string/split (name x) #"\.")
985-
(map symbol) vec)
986-
'prototype)})
986+
(let [props (->pre x)
987+
[xs y] ((juxt butlast last) props)]
988+
(with-meta 'js
989+
{:prefix (vec (concat xs [(with-meta y {:ctor true})]))}))
987990
x))
988991

989992
(defn ->type-set
@@ -1030,46 +1033,89 @@
10301033
boolean Boolean
10311034
symbol Symbol})
10321035

1033-
(defn has-extern?*
1036+
(defn resolve-extern
1037+
"Given a foreign js property list, return a resolved js property list and the
1038+
extern var info"
1039+
([pre]
1040+
(resolve-extern pre (get-externs)))
10341041
([pre externs]
1035-
(let [pre (if-some [me (find
1036-
(get-in externs '[Window prototype])
1037-
(first pre))]
1038-
(if-some [tag (-> me first meta :tag)]
1039-
(into [tag 'prototype] (next pre))
1040-
pre)
1041-
pre)]
1042-
(has-extern?* pre externs externs)))
1043-
([pre externs top]
1042+
(resolve-extern pre externs externs {:resolved []}))
1043+
([pre externs top ret]
10441044
(cond
1045-
(empty? pre) true
1045+
(empty? pre) ret
10461046
:else
10471047
(let [x (first pre)
10481048
me (find externs x)]
10491049
(cond
1050-
(not me) false
1050+
(not me) nil
10511051
:else
10521052
(let [[x' externs'] me
1053-
xmeta (meta x')]
1054-
(if (and (= 'Function (:tag xmeta)) (:ctor xmeta))
1055-
(or (has-extern?* (into '[prototype] (next pre)) externs' top)
1056-
(has-extern?* (next pre) externs' top)
1057-
;; check base type if it exists
1058-
(when-let [super (:super xmeta)]
1059-
(has-extern?* (into [super] (next pre)) externs top)))
1060-
(recur (next pre) externs' top))))))))
1053+
info' (meta x')
1054+
ret (cond-> ret
1055+
;; we only care about var info for the last property
1056+
;; also if we already added it, don't override it
1057+
;; because we're now resolving type information
1058+
;; not instance information anymore
1059+
;; i.e. [console] -> [Console] but :tag is Console _not_ Function vs.
1060+
;; [console log] -> [Console prototype log] where :tag is Function
1061+
(and (empty? (next pre))
1062+
(not (contains? ret :info)))
1063+
(assoc :info info'))]
1064+
;; handle actual occurrences of types, i.e. `Console`
1065+
(if (and (or (:ctor info') (:iface info')) (= 'Function (:tag info')))
1066+
(or
1067+
;; then check for "static" property
1068+
(resolve-extern (next pre) externs' top
1069+
(update ret :resolved conj x))
1070+
1071+
;; first look for a property on the prototype
1072+
(resolve-extern (into '[prototype] (next pre)) externs' top
1073+
(update ret :resolved conj x))
1074+
1075+
;; finally check the super class if there is one
1076+
(when-let [super (:super info')]
1077+
(resolve-extern (into [super] (next pre)) externs top
1078+
(assoc ret :resolved []))))
1079+
1080+
(or
1081+
;; If the tag of the property isn't Function or undefined,
1082+
;; try to resolve it similar to the super case above,
1083+
;; this handles singleton cases like `console`
1084+
(let [tag (:tag info')]
1085+
(when (and tag (not (contains? '#{Function undefined} tag)))
1086+
;; check prefix first, during cljs.externs parsing we always generate prefixes
1087+
;; for tags because of types like webCrypto.Crypto
1088+
(resolve-extern (into (or (-> tag meta :prefix) [tag]) (next pre)) externs top
1089+
(assoc ret :resolved []))))
1090+
1091+
;; assume static property
1092+
(recur (next pre) externs' top
1093+
(update ret :resolved conj x))))))))))
1094+
1095+
(defn normalize-unresolved-prefix
1096+
[pre]
1097+
(cond-> pre
1098+
(< 1 (count pre))
1099+
(cond->
1100+
(-> pre pop peek meta :ctor)
1101+
(-> pop
1102+
(conj 'prototype)
1103+
(conj (peek pre))))))
1104+
1105+
(defn has-extern?*
1106+
[pre externs]
1107+
(boolean (resolve-extern pre externs)))
10611108

10621109
(defn has-extern?
10631110
([pre]
10641111
(has-extern? pre (get-externs)))
10651112
([pre externs]
10661113
(or (has-extern?* pre externs)
1067-
(when (= 1 (count pre))
1068-
(let [x (first pre)]
1069-
(or (get-in externs (conj '[Window prototype] x))
1070-
(get-in externs (conj '[Number] x)))))
10711114
(-> (last pre) str (string/starts-with? "cljs$")))))
10721115

1116+
(defn lift-tag-to-js [tag]
1117+
(symbol "js" (str (alias->type tag tag))))
1118+
10731119
(defn js-tag
10741120
([pre]
10751121
(js-tag pre :tag))
@@ -1078,12 +1124,13 @@
10781124
([pre tag-type externs]
10791125
(js-tag pre tag-type externs externs))
10801126
([pre tag-type externs top]
1081-
(when-let [[p externs' :as me] (find externs (first pre))]
1082-
(let [tag (-> p meta tag-type)]
1083-
(if (= (count pre) 1)
1084-
(when tag (symbol "js" (str (alias->type tag tag))))
1085-
(or (js-tag (next pre) tag-type externs' top)
1086-
(js-tag (into '[prototype] (next pre)) tag-type (get top tag) top)))))))
1127+
(when-let [tag (get-in (resolve-extern pre externs) [:info tag-type])]
1128+
(case tag
1129+
;; don't lift these, analyze-dot will raise them for analysis
1130+
;; representing these types as js/Foo is a hassle as it widens the
1131+
;; return types unnecessarily i.e. #{boolean js/Boolean}
1132+
(boolean number string) tag
1133+
(lift-tag-to-js tag)))))
10871134

10881135
(defn dotted-symbol? [sym]
10891136
(let [s (str sym)]
@@ -1274,8 +1321,9 @@
12741321
(assoc shadowed-by-local :op :local))
12751322

12761323
:else
1277-
(let [pre (->> (string/split (name sym) #"\.") (map symbol) vec)]
1278-
(when (and (not (has-extern? pre))
1324+
(let [pre (->> (string/split (name sym) #"\.") (map symbol) vec)
1325+
res (resolve-extern (->> (string/split (name sym) #"\.") (map symbol) vec))]
1326+
(when (and (not res)
12791327
;; ignore exists? usage
12801328
(not (-> sym meta ::no-resolve)))
12811329
(swap! env/*compiler* update-in
@@ -1284,10 +1332,12 @@
12841332
{:name sym
12851333
:op :js-var
12861334
:ns 'js
1287-
:tag (with-meta (or (js-tag pre) (:tag (meta sym)) 'js) {:prefix pre})}
1335+
:tag (with-meta (or (js-tag pre) (:tag (meta sym)) 'js)
1336+
{:prefix pre
1337+
:ctor (-> res :info :ctor)})}
12881338
(when-let [ret-tag (js-tag pre :ret-tag)]
12891339
{:js-fn-var true
1290-
:ret-tag ret-tag})))))
1340+
:ret-tag ret-tag})))))
12911341
(let [s (str sym)
12921342
lb (handle-symbol-local sym (get locals sym))
12931343
current-ns (-> env :ns :name)]
@@ -2585,12 +2635,12 @@
25852635
:children [:expr]}))
25862636

25872637
(def js-prim-ctor->tag
2588-
'{js/Object object
2589-
js/String string
2590-
js/Array array
2591-
js/Number number
2638+
'{js/Object object
2639+
js/String string
2640+
js/Array array
2641+
js/Number number
25922642
js/Function function
2593-
js/Boolean boolean})
2643+
js/Boolean boolean})
25942644

25952645
(defn prim-ctor?
25962646
"Test whether a tag is a constructor for a JS primitive"
@@ -3543,13 +3593,25 @@
35433593
(list* '. dot-form) " with classification "
35443594
(classify-dot-form dot-form))))))
35453595

3596+
;; this only for a smaller set of types that we want to infer
3597+
;; we don't generally want to consider function for example, these
3598+
;; specific cases are ones we either try to optimize or validate
3599+
(def ^{:private true}
3600+
tag->js-prim-ctor
3601+
'{string js/String
3602+
array js/Array
3603+
number js/Number
3604+
boolean js/Boolean})
3605+
35463606
(defn analyze-dot [env target field member+ form]
35473607
(let [v [target field member+]
35483608
{:keys [dot-action target method field args]} (build-dot-form v)
35493609
enve (assoc env :context :expr)
35503610
targetexpr (analyze enve target)
35513611
form-meta (meta form)
3552-
target-tag (:tag targetexpr)
3612+
target-tag (as-> (:tag targetexpr) $
3613+
(or (some-> $ meta :ctor lift-tag-to-js)
3614+
(tag->js-prim-ctor $ $)))
35533615
prop (or field method)
35543616
tag (or (:tag form-meta)
35553617
(and (js-tag? target-tag)
@@ -3581,7 +3643,8 @@
35813643
(let [pre (-> tag meta :prefix)]
35823644
(when-not (has-extern? pre)
35833645
(swap! env/*compiler* update-in
3584-
(into [::namespaces (-> env :ns :name) :externs] pre) merge {}))))
3646+
(into [::namespaces (-> env :ns :name) :externs]
3647+
(normalize-unresolved-prefix pre)) merge {}))))
35853648
(case dot-action
35863649
::access (let [children [:target]]
35873650
{:op :host-field

src/main/clojure/cljs/analyzer/api.cljc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,15 @@
218218
([state]
219219
(keys (get @state ::ana/namespaces))))
220220

221+
(defn resolve-extern
222+
"Given a symbol attempt to look it up in the provided externs"
223+
([sym]
224+
(resolve-extern env/*compiler* sym))
225+
([state sym]
226+
(let [pre (ana/->pre sym)]
227+
(env/with-compiler-env state
228+
(:info (ana/resolve-extern pre))))))
229+
221230
(defn find-ns
222231
"Given a namespace return the corresponding namespace analysis map. Analagous
223232
to clojure.core/find-ns."

0 commit comments

Comments
 (0)