Skip to content

Commit b5147bf

Browse files
anmonteirodnolen
authored andcommitted
CLJS-1346: Support require outside of ns
1 parent 3ab6de4 commit b5147bf

File tree

10 files changed

+344
-53
lines changed

10 files changed

+344
-53
lines changed

src/main/cljs/cljs/core.cljs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10361,7 +10361,7 @@ reduces them without incurring seq initialization"
1036110361
[x]
1036210362
(contains?
1036310363
'#{if def fn* do let* loop* letfn* throw try catch finally
10364-
recur new set! ns deftype* defrecord* . js* & quote var}
10364+
recur new set! ns deftype* defrecord* . js* & quote var ns*}
1036510365
x))
1036610366

1036710367
(defn test

src/main/cljs/cljs/js.cljs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
; You must not remove this notice, or any other, from this software.
88

99
(ns cljs.js
10+
(:refer-clojure :exclude [require])
1011
(:require-macros [cljs.js :refer [dump-core]]
1112
[cljs.env.macros :as env])
1213
(:require [clojure.string :as string]

src/main/cljs/cljs/nodejs.cljs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
; Projects compiled with :target :nodejs can 'require' this namespace
1010
; to get the nodejs globals loaded into cljs.nodejs and get
1111
; ClojureScript's 'print' set up correctly.
12-
(ns cljs.nodejs)
12+
(ns cljs.nodejs
13+
(:refer-clojure :exclude [require]))
1314

1415
; Define namespaced references to Node's externed globals:
1516
(def require (js* "require"))

src/main/clojure/cljs/analyzer.cljc

Lines changed: 168 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#?(:cljs (:require-macros
1313
[cljs.analyzer.macros
1414
:refer [no-warn wrapping-errors
15-
disallowing-recur allowing-redef]]
15+
disallowing-recur allowing-redef disallowing-ns*]]
1616
[cljs.env.macros :refer [ensure]]))
1717
#?(:clj (:require [cljs.util :as util :refer [ns->relpath topo-sort]]
1818
[clojure.java.io :as io]
@@ -36,6 +36,8 @@
3636
[java.util.regex Pattern]
3737
[java.net URL]
3838
[java.lang Throwable]
39+
[java.security MessageDigest]
40+
[javax.xml.bind DatatypeConverter]
3941
[clojure.lang Namespace Var LazySeq ArityException]
4042
[cljs.tagged_literals JSValue])))
4143

@@ -878,11 +880,12 @@
878880
(declare analyze analyze-symbol analyze-seq)
879881

880882
(def specials '#{if def fn* do let* loop* letfn* throw try recur new set!
881-
ns deftype* defrecord* . js* & quote case* var})
883+
ns deftype* defrecord* . js* & quote case* var ns*})
882884

883885
(def ^:dynamic *recur-frames* nil)
884886
(def ^:dynamic *loop-lets* ())
885887
(def ^:dynamic *allow-redef* false)
888+
(def ^:dynamic *allow-ns* true)
886889

887890
#?(:clj
888891
(defmacro disallowing-recur [& body]
@@ -892,6 +895,10 @@
892895
(defmacro allowing-redef [& body]
893896
`(binding [*allow-redef* true] ~@body)))
894897

898+
#?(:clj
899+
(defmacro disallowing-ns* [& body]
900+
`(binding [*allow-ns* false] ~@body)))
901+
895902
;; TODO: move this logic out - David
896903
(defn analyze-keyword
897904
[env sym]
@@ -1213,7 +1220,8 @@
12131220
(when dynamic {:dynamic true})
12141221
(source-info var-name env)))
12151222
(disallowing-recur
1216-
(analyze (assoc env :context :expr) (:init args) sym)))
1223+
(disallowing-ns*
1224+
(analyze (assoc env :context :expr) (:init args) sym))))
12171225
fn-var? (and init-expr (= (:op init-expr) :fn))
12181226
tag (if fn-var?
12191227
(or (:ret-tag init-expr) tag)
@@ -1404,7 +1412,7 @@
14041412
menv (merge menv
14051413
{:protocol-impl proto-impl
14061414
:protocol-inline proto-inline})
1407-
methods (map #(analyze-fn-method menv locals % type) meths)
1415+
methods (map #(disallowing-ns* (analyze-fn-method menv locals % type)) meths)
14081416
mfa (apply max (map :max-fixed-arity methods))
14091417
variadic (boolean (some :variadic methods))
14101418
locals (if-not (nil? name)
@@ -1418,7 +1426,7 @@
14181426
methods (if-not (nil? name)
14191427
;; a second pass with knowledge of our function-ness/arity
14201428
;; lets us optimize self calls
1421-
(analyze-fn-methods-pass2 menv locals type meths)
1429+
(disallowing-ns* (analyze-fn-methods-pass2 menv locals type meths))
14221430
methods)
14231431
form (vary-meta form dissoc ::protocol-impl ::protocol-inline ::type)
14241432
js-doc (when (true? variadic)
@@ -2076,10 +2084,7 @@
20762084
(util/ns->source sym')))))))
20772085

20782086
#?(:clj
2079-
(defn rewrite-cljs-aliases
2080-
"Alias non-existing clojure.* namespaces to existing cljs.* namespaces if
2081-
possible."
2082-
[args]
2087+
(defn process-rewrite-form [[k & specs :as form]]
20832088
(letfn [(process-spec [maybe-spec]
20842089
(let [[lib & xs] (if (sequential? maybe-spec)
20852090
maybe-spec
@@ -2088,12 +2093,25 @@
20882093
(let [lib' (clj-ns->cljs-ns lib)
20892094
spec (cons lib' xs)]
20902095
(into (if xs [spec] []) [(list lib' :as lib)]))
2091-
[maybe-spec])))
2092-
(process-form [[k & specs :as form]]
2093-
(if (#{:use :require} k)
2094-
(cons k (mapcat process-spec specs))
2095-
form))]
2096-
(map process-form args))))
2096+
[maybe-spec])))]
2097+
(if (#{:use :require} k)
2098+
(cons k (mapcat process-spec specs))
2099+
form))))
2100+
2101+
#?(:clj
2102+
(defn rewrite-cljs-aliases
2103+
"Alias non-existing clojure.* namespaces to existing cljs.* namespaces if
2104+
possible."
2105+
[args]
2106+
(map process-rewrite-form args)))
2107+
2108+
(defn canonicalize-specs [specs]
2109+
(letfn [(canonicalize [quoted-spec-or-kw]
2110+
(if (keyword? quoted-spec-or-kw)
2111+
quoted-spec-or-kw
2112+
(as-> (second quoted-spec-or-kw) spec
2113+
(if (vector? spec) spec [spec]))))]
2114+
(map canonicalize specs)))
20972115

20982116
(defn desugar-ns-specs
20992117
"Given an original set of ns specs desugar :include-macros and :refer-macros
@@ -2166,6 +2184,8 @@
21662184

21672185
(defmethod parse 'ns
21682186
[_ env [_ name & args :as form] _ opts]
2187+
(when-not *allow-ns*
2188+
(throw (error env "Namespace declarations must be at the top-level.")))
21692189
(when-not (symbol? name)
21702190
(throw (error env "Namespaces must be named by a symbol.")))
21712191
(let [name (cond-> name (:macros-ns opts) macro-ns-name)]
@@ -2285,6 +2305,93 @@
22852305
(update-in [:requires]
22862306
(fn [m] (with-meta m {(@reload :require) true})))))))))
22872307

2308+
(defmethod parse 'ns*
2309+
[_ env [_ quoted-specs :as form] _ opts]
2310+
(when-let [not-quoted (->> (remove keyword? quoted-specs)
2311+
(filter #(not= 'quote (first %)) )
2312+
first)]
2313+
(throw (error env (str "Arguments to " (name (first quoted-specs))
2314+
" must be quoted. Offending spec: " not-quoted))))
2315+
(when-not *allow-ns*
2316+
(throw (error env "Namespace declarations must be at the top-level.")))
2317+
(let [specs (canonicalize-specs quoted-specs)
2318+
name 'cljs.user
2319+
args (desugar-ns-specs
2320+
#?(:clj (list (process-rewrite-form
2321+
specs))
2322+
:cljs (list specs)))
2323+
{excludes :excludes core-renames :renames} (parse-ns-excludes env args)
2324+
core-renames (reduce (fn [m [original renamed]]
2325+
(assoc m renamed (symbol "cljs.core" (str original))))
2326+
{} core-renames)
2327+
deps (atom #{})
2328+
aliases (atom {:fns {} :macros {}})
2329+
spec-parsers {:require (partial parse-require-spec env false deps aliases)
2330+
:require-macros (partial parse-require-spec env true deps aliases)
2331+
:use (comp (partial parse-require-spec env false deps aliases)
2332+
(partial use->require env))
2333+
:use-macros (comp (partial parse-require-spec env true deps aliases)
2334+
(partial use->require env))
2335+
:import (partial parse-import-spec env deps)}
2336+
reload (atom {:use nil :require nil :use-macros nil :require-macros nil})
2337+
reloads (atom {})
2338+
{uses :use requires :require renames :rename
2339+
use-macros :use-macros require-macros :require-macros
2340+
rename-macros :rename-macros imports :import :as params}
2341+
(reduce
2342+
(fn [m [k & libs]]
2343+
;; check for spec type reloads
2344+
(when-not (= :import k)
2345+
(when (some #{:reload} libs)
2346+
(swap! reload assoc k :reload))
2347+
(when (some #{:reload-all} libs)
2348+
(swap! reload assoc k :reload-all)))
2349+
;; check for individual ns reloads from REPL interactions
2350+
(when-let [xs (seq (filter #(-> % meta :reload) libs))]
2351+
(swap! reloads assoc k
2352+
(zipmap (map first xs) (map #(-> % meta :reload) xs))))
2353+
(apply merge-with merge m
2354+
(map (spec-parsers k)
2355+
(remove #{:reload :reload-all} libs))))
2356+
{} (remove (fn [[r]] (= r :refer-clojure)) args))]
2357+
(set! *cljs-ns* 'cljs.user)
2358+
(let [require-info
2359+
{:name name
2360+
:excludes excludes
2361+
:use-macros use-macros
2362+
:require-macros require-macros
2363+
:rename-macros rename-macros
2364+
:uses uses
2365+
:requires requires
2366+
:renames (merge renames core-renames)
2367+
:imports imports}
2368+
ns-info
2369+
(let [ns-info' (get-in @env/*compiler* [::namespaces name])]
2370+
(if (pos? (count ns-info'))
2371+
(let [merge-keys
2372+
[:use-macros :require-macros :rename-macros
2373+
:uses :requires :renames :imports]]
2374+
(merge
2375+
ns-info'
2376+
(merge-with merge
2377+
(select-keys ns-info' merge-keys)
2378+
(select-keys require-info merge-keys))))
2379+
require-info))]
2380+
(swap! env/*compiler* update-in [::namespaces name] merge ns-info)
2381+
(merge {:op :ns*
2382+
:env env
2383+
:form form
2384+
:deps @deps
2385+
:reload @reload
2386+
:reloads @reloads}
2387+
(cond-> require-info
2388+
(@reload :use)
2389+
(update-in [:uses]
2390+
(fn [m] (with-meta m {(@reload :use) true})))
2391+
(@reload :require)
2392+
(update-in [:requires]
2393+
(fn [m] (with-meta m {(@reload :require) true}))))))))
2394+
22882395
(defn parse-type
22892396
[op env [_ tsym fields pmasks body :as form]]
22902397
(let [t (:name (resolve-var (dissoc env :locals) tsym))
@@ -2853,7 +2960,7 @@
28532960
#?(:clj
28542961
(defn ns-side-effects
28552962
[env {:keys [op] :as ast} opts]
2856-
(if (= :ns op)
2963+
(if (#{:ns :ns*} op)
28572964
(let [{:keys [name deps uses require-macros use-macros reload reloads]} ast]
28582965
(when (and *analyze-deps* (seq deps))
28592966
(analyze-deps name deps env (dissoc opts :macros-ns)))
@@ -3035,6 +3142,20 @@
30353142
[(forms-seq*) rdr]
30363143
(forms-seq*))))))
30373144

3145+
#?(:clj
3146+
(defn gen-user-ns [src]
3147+
(let [name (str src)
3148+
name (.substring name (inc (.lastIndexOf name "/")) (.lastIndexOf name "."))
3149+
digest (MessageDigest/getInstance "SHA-1")]
3150+
(.reset digest)
3151+
(.update digest (.getBytes ^String name "utf8"))
3152+
(symbol
3153+
(str
3154+
"cljs.user$$gen_ns$$_" name
3155+
(->> (DatatypeConverter/printHexBinary (.digest digest))
3156+
(take 7)
3157+
(apply str)))))))
3158+
30383159
#?(:clj
30393160
(defn parse-ns
30403161
"Helper for parsing only the essential namespace information from a
@@ -3075,11 +3196,25 @@
30753196
(try
30763197
(loop [forms (if rdr
30773198
(forms-seq* rdr (source-path src))
3078-
src)]
3199+
src)
3200+
ret (merge
3201+
{:ns (gen-user-ns src)
3202+
:provides [(gen-user-ns src)]
3203+
:file dest
3204+
:source-file (when rdr src)
3205+
:source-forms (when-not rdr src)
3206+
:macros-ns (:macros-ns opts)
3207+
:requires (cond-> #{'cljs.core}
3208+
(get-in @env/*compiler* [:options :emit-constants])
3209+
(conj 'constants-table))}
3210+
(when (and dest (.exists ^File dest))
3211+
{:lines (with-open [reader (io/reader dest)]
3212+
(-> reader line-seq count))}))]
30793213
(if (seq forms)
30803214
(let [env (empty-env)
30813215
ast (no-warn (analyze env (first forms) nil opts))]
3082-
(if (= :ns (:op ast))
3216+
(cond
3217+
(= :ns (:op ast))
30833218
(let [ns-name (:name ast)
30843219
ns-name (if (and (= 'cljs.core ns-name)
30853220
(= "cljc" (util/ext src)))
@@ -3103,8 +3238,14 @@
31033238
(when (and dest (.exists ^File dest))
31043239
{:lines (with-open [reader (io/reader dest)]
31053240
(-> reader line-seq count))})))
3106-
(recur (rest forms))))
3107-
(throw (AssertionError. (str "No ns form found in " src)))))
3241+
3242+
(= :ns* (:op ast))
3243+
(let [deps (merge (:uses ast) (:requires ast))]
3244+
(recur (rest forms)
3245+
(update-in ret [:requires] into (set (vals deps)))))
3246+
3247+
:else ret))
3248+
ret))
31083249
(finally
31093250
(when rdr
31103251
(.close ^Reader rdr))))))]
@@ -3225,8 +3366,14 @@
32253366
(let [form (first forms)
32263367
env (assoc env :ns (get-namespace *cljs-ns*))
32273368
ast (analyze env form nil opts)]
3228-
(if (= (:op ast) :ns)
3369+
(cond
3370+
(= (:op ast) :ns)
32293371
(recur (:name ast) (next forms))
3372+
3373+
(and (nil? ns) (= (:op ast) :ns*))
3374+
(recur (gen-user-ns res) (next forms))
3375+
3376+
:else
32303377
(recur ns (next forms))))
32313378
ns)))]
32323379
(when (and cache (true? (:cache-analysis opts)))

src/main/clojure/cljs/analyzer/macros.clj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,7 @@
5151

5252
(defmacro allowing-redef [& body]
5353
`(binding [cljs.analyzer/*allow-redef* true]
54-
~@body))
54+
~@body))
55+
56+
(defmacro disallowing-ns* [& body]
57+
`(binding [cljs.analyzer/*allow-ns* false] ~@body))

0 commit comments

Comments
 (0)