for anyone interested in reusing some of Typed Clojure's infrastructure for their own type system, https://www.patreon.com/posts/example-type-37870230 shows how to use its analyzer.
@borkdude No idea if this could be useful to the type checking in clj-kondo or not, but in case you don't monitor this channel, and it might be useful.
thanks!
@borkdude if you ever find yourself wanting to partially expand something, it'll probably be useful. I think you use rewrite-clj right? my analyzer is intended for the use-case where you use tools.analyzer, but you need to check intermediate steps.
tools.analyzer is not suited for clj-kondo, it doesn't work with GraalVM unfortunately
oh interesting. my analyzer is a distinct entity from tools.analyzer (w/ no transitive dependency on it), do you know what the issues are? I might be able to fix them.
I think one issue was that tools.analyzer did macroexpansion which effectively uses eval
and eval uses dynamic classloading which is not supported by GraalVM
welp
well, that's bad news 😞
It's basically why I wrote a clojure interpreter 😉
I'm working on a new macroexpand feature in clj-kondo that will be using this interpreter, so it can all still run in the native binary: https://clojureverse.org/t/feedback-wanted-on-new-clj-kondo-macroexpansion-feature/6043 feedback on that is most welcome
nice. I've thought a lot about this kind of thing. in typed clojure, the analyzer can return an ::unanalyzed
AST node, and then custom rules can be registered about how to type check a particular macro.
you'd register rules with this https://github.com/typedclojure/typedclojure/blob/master/typed/clj.checker/src/clojure/core/typed/checker/jvm/check.clj#L258-L282
here's the rule for clojure.core/ns
https://github.com/typedclojure/typedclojure/blob/94e6daefd70320b7effaf37f5d8bfb439c1aad29/typed/lib.clojure/src/clojure/core/typed/ext/clojure/core.clj#L21-L27
I haven't figured out how to properly handle macros that bind variables, but I would go in the direction of extending a type environment and passing that down, rather than your sketch of expanding to a let
(FWIW).
the clj-kondo approach is not only about validating a macro. it's foremost about expanding since it can't do that without eval. but since the user provides the code that macroexpands, it can do all kinds of checking on it as well and throw an exception. I'll probably let the user return an :findings
vector too.
potentially the expand function could get access to locals, etc too. that's why the function signature is a map to a map, to allow for more stuff in the future
yea cool that sounds right to me
I like this direction. does your interpreter expose a macroexpand-1
function?
it does:
$ bb "(defmacro foo [x] (list 'do x x)) (macroexpand-1 '(foo 1))"
(do 1 1)
(bb = babashka which uses the same interpreter)
ah interesting. can you rebind tools.analyzer's macroexpand-1 with that?
you mean, like, alter-var-root?
nah I mean rebind this https://github.com/clojure/tools.analyzer/blob/master/src/main/clojure/clojure/tools/analyzer.clj#L129-L133
it has its own macroexpand-1
that's the direction I was thinking, I'm sure there's a ton of other details
(for graalvm + tools.analyzer compat)
I think this boils down to the reason why clj-kondo doesn't just run the code from a user's source file: because it has to be pure clojure, no references to other namespaces or particular Java classes, protocols, etc
yea ok
appreciate your time and insight!
likewise!