|
980 | 980 | (defn normalize-js-tag [x]
|
981 | 981 | ;; if not 'js, assume constructor
|
982 | 982 | (if-not (= 'js x)
|
983 |
| - (with-meta 'js |
984 |
| - {:prefix (conj (->> (string/split (name x) #"\.") |
985 |
| - (map symbol) vec) |
986 |
| - 'prototype)}) |
| 983 | + (let [props (->> (string/split (name x) #"\.") (map symbol)) |
| 984 | + [xs y] ((juxt butlast last) props)] |
| 985 | + (with-meta 'js |
| 986 | + {:prefix (vec (concat xs [(with-meta y {:ctor true})]))})) |
987 | 987 | x))
|
988 | 988 |
|
989 | 989 | (defn ->type-set
|
|
1030 | 1030 | boolean Boolean
|
1031 | 1031 | symbol Symbol})
|
1032 | 1032 |
|
1033 |
| -(defn has-extern?* |
| 1033 | +(defn resolve-extern |
| 1034 | + "Given a foreign js property list, return a resolved js property list and the |
| 1035 | + extern var info" |
| 1036 | + ([pre] |
| 1037 | + (resolve-extern pre (get-externs))) |
1034 | 1038 | ([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] |
| 1039 | + (resolve-extern pre externs externs {:resolved []})) |
| 1040 | + ([pre externs top ret] |
1044 | 1041 | (cond
|
1045 |
| - (empty? pre) true |
| 1042 | + (empty? pre) ret |
1046 | 1043 | :else
|
1047 | 1044 | (let [x (first pre)
|
1048 | 1045 | me (find externs x)]
|
1049 | 1046 | (cond
|
1050 |
| - (not me) false |
| 1047 | + (not me) nil |
1051 | 1048 | :else
|
1052 | 1049 | (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)))))))) |
| 1050 | + info' (meta x') |
| 1051 | + ret (cond-> ret |
| 1052 | + ;; we only care about var info for the last property |
| 1053 | + ;; also if we already added it, don't override it |
| 1054 | + ;; because we're now resolving type information |
| 1055 | + ;; not instance information anymore |
| 1056 | + ;; i.e. [console] -> [Console] but :tag is Console _not_ Function vs. |
| 1057 | + ;; [console log] -> [Console prototype log] where :tag is Function |
| 1058 | + (and (empty? (next pre)) |
| 1059 | + (not (contains? ret :info))) |
| 1060 | + (assoc :info info'))] |
| 1061 | + ;; handle actual occurrences of types, i.e. `Console` |
| 1062 | + (if (and (or (:ctor info') (:iface info')) (= 'Function (:tag info'))) |
| 1063 | + (or |
| 1064 | + ;; then check for "static" property |
| 1065 | + (resolve-extern (next pre) externs' top |
| 1066 | + (update ret :resolved conj x)) |
| 1067 | + |
| 1068 | + ;; first look for a property on the prototype |
| 1069 | + (resolve-extern (into '[prototype] (next pre)) externs' top |
| 1070 | + (update ret :resolved conj x)) |
| 1071 | + |
| 1072 | + ;; finally check the super class if there is one |
| 1073 | + (when-let [super (:super info')] |
| 1074 | + (resolve-extern (into [super] (next pre)) externs top |
| 1075 | + (assoc ret :resolved [])))) |
| 1076 | + |
| 1077 | + (or |
| 1078 | + ;; If the tag of the property isn't Function or undefined, |
| 1079 | + ;; try to resolve it similar to the super case above, |
| 1080 | + ;; this handles singleton cases like `console` |
| 1081 | + (let [tag (:tag info')] |
| 1082 | + (when (and tag (not (contains? '#{Function undefined} tag))) |
| 1083 | + ;; check prefix first, during cljs.externs parsing we always generate prefixes |
| 1084 | + ;; for tags because of types like webCrypto.Crypto |
| 1085 | + (resolve-extern (into (or (-> tag meta :prefix) [tag]) (next pre)) externs top |
| 1086 | + (assoc ret :resolved [])))) |
| 1087 | + |
| 1088 | + ;; assume static property |
| 1089 | + (recur (next pre) externs' top |
| 1090 | + (update ret :resolved conj x)))))))))) |
| 1091 | + |
| 1092 | +(defn normalize-unresolved-prefix |
| 1093 | + [pre] |
| 1094 | + (cond-> pre |
| 1095 | + (< 1 (count pre)) |
| 1096 | + (cond-> |
| 1097 | + (-> pre pop peek meta :ctor) |
| 1098 | + (-> pop |
| 1099 | + (conj 'prototype) |
| 1100 | + (conj (peek pre)))))) |
| 1101 | + |
| 1102 | +(defn has-extern?* |
| 1103 | + [pre externs] |
| 1104 | + (boolean (resolve-extern pre externs))) |
1061 | 1105 |
|
1062 | 1106 | (defn has-extern?
|
1063 | 1107 | ([pre]
|
1064 | 1108 | (has-extern? pre (get-externs)))
|
1065 | 1109 | ([pre externs]
|
1066 | 1110 | (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))))) |
1071 | 1111 | (-> (last pre) str (string/starts-with? "cljs$")))))
|
1072 | 1112 |
|
| 1113 | +(defn lift-tag-to-js [tag] |
| 1114 | + (symbol "js" (str (alias->type tag tag)))) |
| 1115 | + |
1073 | 1116 | (defn js-tag
|
1074 | 1117 | ([pre]
|
1075 | 1118 | (js-tag pre :tag))
|
|
1078 | 1121 | ([pre tag-type externs]
|
1079 | 1122 | (js-tag pre tag-type externs externs))
|
1080 | 1123 | ([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))))))) |
| 1124 | + (when-let [tag (get-in (resolve-extern pre externs) [:info tag-type])] |
| 1125 | + (case tag |
| 1126 | + ;; don't lift these, analyze-dot will raise them for analysis |
| 1127 | + ;; representing these types as js/Foo is a hassle as it widens the |
| 1128 | + ;; return types unnecessarily i.e. #{boolean js/Boolean} |
| 1129 | + (boolean number string) tag |
| 1130 | + (lift-tag-to-js tag))))) |
1087 | 1131 |
|
1088 | 1132 | (defn dotted-symbol? [sym]
|
1089 | 1133 | (let [s (str sym)]
|
|
1274 | 1318 | (assoc shadowed-by-local :op :local))
|
1275 | 1319 |
|
1276 | 1320 | :else
|
1277 |
| - (let [pre (->> (string/split (name sym) #"\.") (map symbol) vec)] |
1278 |
| - (when (and (not (has-extern? pre)) |
| 1321 | + (let [pre (->> (string/split (name sym) #"\.") (map symbol) vec) |
| 1322 | + res (resolve-extern (->> (string/split (name sym) #"\.") (map symbol) vec))] |
| 1323 | + (when (and (not res) |
1279 | 1324 | ;; ignore exists? usage
|
1280 | 1325 | (not (-> sym meta ::no-resolve)))
|
1281 | 1326 | (swap! env/*compiler* update-in
|
|
1284 | 1329 | {:name sym
|
1285 | 1330 | :op :js-var
|
1286 | 1331 | :ns 'js
|
1287 |
| - :tag (with-meta (or (js-tag pre) (:tag (meta sym)) 'js) {:prefix pre})} |
| 1332 | + :tag (with-meta (or (js-tag pre) (:tag (meta sym)) 'js) |
| 1333 | + {:prefix pre |
| 1334 | + :ctor (-> res :info :ctor)})} |
1288 | 1335 | (when-let [ret-tag (js-tag pre :ret-tag)]
|
1289 | 1336 | {:js-fn-var true
|
1290 |
| - :ret-tag ret-tag}))))) |
| 1337 | + :ret-tag ret-tag}))))) |
1291 | 1338 | (let [s (str sym)
|
1292 | 1339 | lb (handle-symbol-local sym (get locals sym))
|
1293 | 1340 | current-ns (-> env :ns :name)]
|
|
2585 | 2632 | :children [:expr]}))
|
2586 | 2633 |
|
2587 | 2634 | (def js-prim-ctor->tag
|
2588 |
| - '{js/Object object |
2589 |
| - js/String string |
2590 |
| - js/Array array |
2591 |
| - js/Number number |
| 2635 | + '{js/Object object |
| 2636 | + js/String string |
| 2637 | + js/Array array |
| 2638 | + js/Number number |
2592 | 2639 | js/Function function
|
2593 |
| - js/Boolean boolean}) |
| 2640 | + js/Boolean boolean}) |
2594 | 2641 |
|
2595 | 2642 | (defn prim-ctor?
|
2596 | 2643 | "Test whether a tag is a constructor for a JS primitive"
|
|
3543 | 3590 | (list* '. dot-form) " with classification "
|
3544 | 3591 | (classify-dot-form dot-form))))))
|
3545 | 3592 |
|
| 3593 | +;; this only for a smaller set of types that we want to infer |
| 3594 | +;; we don't generally want to consider function for example, these |
| 3595 | +;; specific cases are ones we either try to optimize or validate |
| 3596 | +(def ^{:private true} |
| 3597 | + tag->js-prim-ctor |
| 3598 | + '{string js/String |
| 3599 | + array js/Array |
| 3600 | + number js/Number |
| 3601 | + boolean js/Boolean}) |
| 3602 | + |
3546 | 3603 | (defn analyze-dot [env target field member+ form]
|
3547 | 3604 | (let [v [target field member+]
|
3548 | 3605 | {:keys [dot-action target method field args]} (build-dot-form v)
|
3549 | 3606 | enve (assoc env :context :expr)
|
3550 | 3607 | targetexpr (analyze enve target)
|
3551 | 3608 | form-meta (meta form)
|
3552 |
| - target-tag (:tag targetexpr) |
| 3609 | + target-tag (as-> (:tag targetexpr) $ |
| 3610 | + (or (some-> $ meta :ctor lift-tag-to-js) |
| 3611 | + (tag->js-prim-ctor $ $))) |
3553 | 3612 | prop (or field method)
|
3554 | 3613 | tag (or (:tag form-meta)
|
3555 | 3614 | (and (js-tag? target-tag)
|
|
3581 | 3640 | (let [pre (-> tag meta :prefix)]
|
3582 | 3641 | (when-not (has-extern? pre)
|
3583 | 3642 | (swap! env/*compiler* update-in
|
3584 |
| - (into [::namespaces (-> env :ns :name) :externs] pre) merge {})))) |
| 3643 | + (into [::namespaces (-> env :ns :name) :externs] |
| 3644 | + (normalize-unresolved-prefix pre)) merge {})))) |
3585 | 3645 | (case dot-action
|
3586 | 3646 | ::access (let [children [:target]]
|
3587 | 3647 | {:op :host-field
|
|
0 commit comments