Namespace find failure macroexpanding for require form
Description
Environment
Attachments
- 13 Jun 2021, 10:16 AM
Activity
David NolenJuly 17, 2021 at 4:01 AM
David NolenJuly 17, 2021 at 3:39 AM
I spent a bit of time looking at this today. One thing that’s probably not completely obvious is that the point of parse-ns
is to have a thing that can compute the dependencies of a namespace w/o analyzing the whole source file. This is required for builds - i.e. what are the files that need to be compiled. We start with main and use parse-ns
to figure out the dependency graph quickly.
The problem is that the moment you introduce macros that can require, parse-ns
cannot reliably do this job without loading macros. This also complicates self-hosted parity because macros will have to be loaded asynchronously.
There’s also the look-ahead problem which is not solved by the proposed patch - https://clojure.atlassian.net/browse/CLJS-3312
Given these issues and what this follow-up patch has to do I think this is getting pretty messy and we should probably just back out https://clojure.atlassian.net/browse/CLJS-3276 for now.
I thought this would be easy due to the work we did for cljs.user
around loading multiple cljs.user
files and supporting require
usage. But now I see they are fundamentally different problems - the cljs.user
case works because it’s a special syntax for writing an implicit ns
form. But introducing macros brings a whole level of new complexity.
Arne BrasseurJune 13, 2021 at 10:34 AM
I’ve attached a patch but not sure it’s good enough.
parse-ns
does not generally analyze deps (unless explicitly asked to do so, but most of the time we don’t want that because it’s costly)parse-ns
does expand macros following the ns form to support the use case of https://clojure.atlassian.net/browse/CLJS-3276 (injecting namespaces into the build)macros like
clojure.test/test-ns-block
callcljs.analyzer/find-ns
, but if they are being expanded as part ofparse-ns
then not all namespace information is available yet in the compiler env, because we didn’t analyze dependencies.
The current patch makes sure that when parse-ns
analyzes successive forms for a given namespace (ns, ns*, require, import, etc), that namespaces that are referenced by previous forms are added to the compiler env (at least by name), so a macro like test-ns-block
will find that namespace (it will look like an empty namespace). It will get expanded again when the namespace is fully analyzed later on.
This fixes the case that @Mike Fikes illustrated above, where you (:require [foo.tests)) (run-tests 'foo.tests)
, but if foo.tests
is required transitively then this will break, so this will probably still break some existing builds.
I’m starting to think that maybe https://clojure.atlassian.net/browse/CLJS-3276 should be opt-in? Maybe with metadata on the macro? I know that’s not particularly pretty, but it’s a fairly special use case anyway.
There might be a more elegant solution, but the current analyzer implementation is somewhat hard to follow. I’d have to do a better analysis of the different places where parse-ns
is used. Currently it seems it’s called in a few different places to get information about a namespace before it’s fully analyzed, and then again as part of the actual analysis process. Potentially only that last call really needs these potentially-requiring macros to be expanded.
deps.edn
:{:deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}}
src/foo/core.clj
:(ns foo.core (:require [cljs.test :refer-macros [run-tests]] [foo.tests])) (run-tests 'foo.tests)
src/foo/tests.cljs
:(ns foo.tests)
Repro:
% clj -M -m cljs.main -re node -r ClojureScript 1.10.866 cljs.user=> (require 'foo.core) Unexpected error (AssertionError) macroexpanding cljs.test/test-ns-block at (REPL:6:1). Assert failed: Namespace foo.tests does not exist (ns foo.core (ana-api/find-ns ns) cljs.user=>
Note that this is a regression related to CLJS-3276.
It can be worked around I a couple of ways:
Insert a non-macro form in the code prior to the
run-tests
macro form (say, the number1
). (This breaks the eager macroexpansion chain in CLJS-3276)Use
:refer
instead of:refer-macros
in thens
form when referringrun-tests
.