Skip to content

Commit a8422ee

Browse files
plexusswannodette
authored andcommitted
CLJS-3276 Support macros that expand to require statements
Support this pattern: (ns foo.bar (:require-macros [foo.baz :refer [macro-that-expands-to-require]])) (macro-that-expands-to-require) To do so we adapt ana/parse-ns to look for consecutive :ns/:ns* ops (ns and require forms), until a non :ns/:ns* form is encountered. The information of consecutive forms are merged so the ns-info that parse-ns returns is complete. This was already happening for :ns* ops (require forms), but not for :ns forms. This means that parse-ns analyzes/parses at least one form beyond the last ns/require form, but this can be problematic since this modifies the compiler env. For instance if the form after ns is a (def ^const ...) then analyzing this twice will cause an error. Hence the check to only analyze macro forms or ns/require forms. This should also help in general to avoid unnecessary work. To make sure the invocation is seen as a macro, and is able to be expanded, we need to keep track of namespace information in the env across multiple :ns/:ns* ops. When encountering a macro invocation we load the macro ns on the fly by invoking the ns-side-effects analyzer pass directly. After this we can call analyze on the form.
1 parent a4673b8 commit a8422ee

File tree

4 files changed

+71
-28
lines changed

4 files changed

+71
-28
lines changed

src/main/clojure/cljs/analyzer.cljc

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4329,6 +4329,19 @@
43294329
(.lastIndexOf full-name "."))]
43304330
(symbol (str "cljs.user." name (util/content-sha full-name 7)))))))
43314331

4332+
#?(:clj
4333+
(defn macro-call? [form env]
4334+
(when (and (seq? form) (seq form) (and (symbol? (first form))))
4335+
(let [sym (first form)
4336+
nstr (namespace sym)]
4337+
(or (and (some? nstr)
4338+
(some? (gets env :ns :require-macros (symbol nstr))))
4339+
(some? (gets env :ns :rename-macros sym))
4340+
(some? (gets env :ns :use-macros sym)))))))
4341+
4342+
#?(:clj
4343+
(declare ns-side-effects macroexpand-1))
4344+
43324345
#?(:clj
43334346
(defn ^:dynamic parse-ns
43344347
"Helper for parsing only the essential namespace information from a
@@ -4354,6 +4367,9 @@
43544367
(binding [env/*compiler* (if (false? (:restore opts))
43554368
env/*compiler*
43564369
(atom @env/*compiler*))
4370+
*file-defs* nil
4371+
#?@(:clj [*unchecked-if* false
4372+
*unchecked-arrays* false])
43574373
*cljs-ns* 'cljs.user
43584374
*cljs-file* src
43594375
*macro-infer*
@@ -4370,7 +4386,8 @@
43704386
false)]
43714387
(let [rdr (when-not (sequential? src) (io/reader src))]
43724388
(try
4373-
(loop [forms (if rdr
4389+
(loop [env (empty-env)
4390+
forms (if rdr
43744391
(forms-seq* rdr (source-path src))
43754392
src)
43764393
ret (merge
@@ -4385,43 +4402,54 @@
43854402
{:lines (with-open [reader (io/reader dest)]
43864403
(-> reader line-seq count))}))]
43874404
(if (seq forms)
4388-
(let [env (empty-env)
4389-
ast (no-warn (analyze env (first forms) nil opts))]
4405+
(let [form (first forms)
4406+
macro? (macro-call? form env)
4407+
env (if macro?
4408+
(binding [*load-macros* true]
4409+
(assoc (:env (ns-side-effects env (:ast ret) opts)) :ns (:ns env)))
4410+
env)
4411+
ast (when (or macro? (and (seq? form) ('#{ns ns* require use require-macros} (first form))))
4412+
(no-warn (analyze env form nil opts)))
4413+
env (assoc (:env ast) :ns (:ns env))]
43904414
(cond
43914415
(= :ns (:op ast))
43924416
(let [ns-name (:name ast)
43934417
ns-name (if (and (= 'cljs.core ns-name)
43944418
(= "cljc" (util/ext src)))
43954419
'cljs.core$macros
43964420
ns-name)
4397-
deps (merge (:uses ast) (:requires ast))]
4398-
(merge
4399-
{:ns (or ns-name 'cljs.user)
4400-
:provides [ns-name]
4401-
:requires (if (= 'cljs.core ns-name)
4402-
(set (vals deps))
4403-
(cond-> (conj (set (vals deps)) 'cljs.core)
4404-
(get-in @env/*compiler* [:options :emit-constants])
4405-
(conj constants-ns-sym)))
4406-
:file dest
4407-
:source-file (when rdr src)
4408-
:source-forms (when-not rdr src)
4409-
:ast ast
4410-
:macros-ns (or (:macros-ns opts)
4411-
(= 'cljs.core$macros ns-name))}
4412-
(when (and dest (.exists ^File dest))
4413-
{:lines (with-open [reader (io/reader dest)]
4414-
(-> reader line-seq count))})))
4421+
deps (merge (:uses ast) (:requires ast))
4422+
env (assoc (:env ast) :ns (dissoc ast :env))]
4423+
(recur env
4424+
(rest forms)
4425+
(cond->
4426+
{:ns (or ns-name 'cljs.user)
4427+
:provides [ns-name]
4428+
:requires (if (= 'cljs.core ns-name)
4429+
(set (vals deps))
4430+
(cond-> (conj (set (vals deps)) 'cljs.core)
4431+
(get-in @env/*compiler* [:options :emit-constants])
4432+
(conj constants-ns-sym)))
4433+
:file dest
4434+
:source-file (when rdr src)
4435+
:source-forms (when-not rdr src)
4436+
:ast ast
4437+
:macros-ns (or (:macros-ns opts)
4438+
(= 'cljs.core$macros ns-name))}
4439+
(and dest (.exists ^File dest))
4440+
(assoc :lines (with-open [reader (io/reader dest)]
4441+
(-> reader line-seq count))))))
44154442

44164443
(= :ns* (:op ast))
44174444
(let [deps (merge (:uses ast) (:requires ast))]
4418-
(recur (rest forms)
4419-
(cond-> (update-in ret [:requires] into (set (vals deps)))
4420-
;; we need to defer generating the user namespace
4421-
;; until we actually need or it will break when
4422-
;; `src` is a sequence of forms - António Monteiro
4423-
(not (:ns ret))
4424-
(assoc :ns (gen-user-ns src) :provides [(gen-user-ns src)]))))
4445+
(recur (:env ast)
4446+
(rest forms)
4447+
(cond-> (update-in ret [:requires] into (set (vals deps)))
4448+
;; we need to defer generating the user namespace
4449+
;; until we actually need or it will break when
4450+
;; `src` is a sequence of forms - António Monteiro
4451+
(not (:ns ret))
4452+
(assoc :ns (gen-user-ns src) :provides [(gen-user-ns src)]))))
44254453

44264454
:else ret))
44274455
ret))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(ns cljs-3276.foo)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(ns cljs-3276.macros)
2+
3+
(defmacro macro-that-requires []
4+
`(require 'cljs-3276.foo))

src/test/clojure/cljs/analyzer_tests.clj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,3 +1492,13 @@
14921492
'[(ns test.foo
14931493
(:import goog))]))
14941494
(is (= {} (get-in @cenv [::ana/namespaces 'test.foo :imports])))))
1495+
1496+
(deftest test-cljs-3276-require-from-macro
1497+
(let [cenv (env/default-compiler-env)]
1498+
(env/with-compiler-env cenv
1499+
(ana/analyze-form-seq
1500+
'[(ns test.foo
1501+
(:require-macros [cljs-3276.macros :refer [macro-that-requires]]))
1502+
(macro-that-requires)]))
1503+
(is (= '{cljs-3276.foo cljs-3276.foo} (get-in @cenv [::ana/namespaces 'test.foo :requires])))
1504+
(is (contains? (get @cenv ::ana/namespaces) 'cljs-3276.foo))))

0 commit comments

Comments
 (0)