|
977 | 977 | (or (= 'js x)
|
978 | 978 | (= "js" (namespace x)))))
|
979 | 979 |
|
| 980 | +(defn ->pre [x] |
| 981 | + (->> (string/split (name x) #"\.") (map symbol))) |
| 982 | + |
980 | 983 | (defn normalize-js-tag [x]
|
981 | 984 | ;; if not 'js, assume constructor
|
982 | 985 | (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})]))})) |
987 | 990 | x))
|
988 | 991 |
|
989 | 992 | (defn ->type-set
|
|
1030 | 1033 | boolean Boolean
|
1031 | 1034 | symbol Symbol})
|
1032 | 1035 |
|
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))) |
1034 | 1041 | ([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] |
1044 | 1044 | (cond
|
1045 |
| - (empty? pre) true |
| 1045 | + (empty? pre) ret |
1046 | 1046 | :else
|
1047 | 1047 | (let [x (first pre)
|
1048 | 1048 | me (find externs x)]
|
1049 | 1049 | (cond
|
1050 |
| - (not me) false |
| 1050 | + (not me) nil |
1051 | 1051 | :else
|
1052 | 1052 | (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))) |
1061 | 1108 |
|
1062 | 1109 | (defn has-extern?
|
1063 | 1110 | ([pre]
|
1064 | 1111 | (has-extern? pre (get-externs)))
|
1065 | 1112 | ([pre externs]
|
1066 | 1113 | (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 | 1114 | (-> (last pre) str (string/starts-with? "cljs$")))))
|
1072 | 1115 |
|
| 1116 | +(defn lift-tag-to-js [tag] |
| 1117 | + (symbol "js" (str (alias->type tag tag)))) |
| 1118 | + |
1073 | 1119 | (defn js-tag
|
1074 | 1120 | ([pre]
|
1075 | 1121 | (js-tag pre :tag))
|
|
1078 | 1124 | ([pre tag-type externs]
|
1079 | 1125 | (js-tag pre tag-type externs externs))
|
1080 | 1126 | ([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))))) |
1087 | 1134 |
|
1088 | 1135 | (defn dotted-symbol? [sym]
|
1089 | 1136 | (let [s (str sym)]
|
|
1274 | 1321 | (assoc shadowed-by-local :op :local))
|
1275 | 1322 |
|
1276 | 1323 | :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) |
1279 | 1327 | ;; ignore exists? usage
|
1280 | 1328 | (not (-> sym meta ::no-resolve)))
|
1281 | 1329 | (swap! env/*compiler* update-in
|
|
1284 | 1332 | {:name sym
|
1285 | 1333 | :op :js-var
|
1286 | 1334 | :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)})} |
1288 | 1338 | (when-let [ret-tag (js-tag pre :ret-tag)]
|
1289 | 1339 | {:js-fn-var true
|
1290 |
| - :ret-tag ret-tag}))))) |
| 1340 | + :ret-tag ret-tag}))))) |
1291 | 1341 | (let [s (str sym)
|
1292 | 1342 | lb (handle-symbol-local sym (get locals sym))
|
1293 | 1343 | current-ns (-> env :ns :name)]
|
|
2585 | 2635 | :children [:expr]}))
|
2586 | 2636 |
|
2587 | 2637 | (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 |
2592 | 2642 | js/Function function
|
2593 |
| - js/Boolean boolean}) |
| 2643 | + js/Boolean boolean}) |
2594 | 2644 |
|
2595 | 2645 | (defn prim-ctor?
|
2596 | 2646 | "Test whether a tag is a constructor for a JS primitive"
|
|
3543 | 3593 | (list* '. dot-form) " with classification "
|
3544 | 3594 | (classify-dot-form dot-form))))))
|
3545 | 3595 |
|
| 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 | + |
3546 | 3606 | (defn analyze-dot [env target field member+ form]
|
3547 | 3607 | (let [v [target field member+]
|
3548 | 3608 | {:keys [dot-action target method field args]} (build-dot-form v)
|
3549 | 3609 | enve (assoc env :context :expr)
|
3550 | 3610 | targetexpr (analyze enve target)
|
3551 | 3611 | 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 $ $))) |
3553 | 3615 | prop (or field method)
|
3554 | 3616 | tag (or (:tag form-meta)
|
3555 | 3617 | (and (js-tag? target-tag)
|
|
3581 | 3643 | (let [pre (-> tag meta :prefix)]
|
3582 | 3644 | (when-not (has-extern? pre)
|
3583 | 3645 | (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 {})))) |
3585 | 3648 | (case dot-action
|
3586 | 3649 | ::access (let [children [:target]]
|
3587 | 3650 | {:op :host-field
|
|
0 commit comments