Fork me on GitHub
#clj-kondo
<
2022-02-11
>
alex06:02:55

Hi there, do you have any tips for circumventing an "Unresolved namespace" error when invoking macros on fully qualified symbols? I have a lazy-component macro that takes a fully qualified symbol for lazily loading modules in cljs. At all of the call-sites, I get an unresolved namespace error due to not requireing the namespace (this is purposeful so that we don't load those namespaces until necessary)

(defmacro lazy-component
  [the-sym]
  `(components.lazy/lazy-component* (shadow.lazy/loadable ~the-sym)))

;; call-site
;; my-app.components.huge-component is not included in the namespace declaration

(lazy/lazy-component components.huge-component-to-be-rendered-async)

borkdude11:02:43

Why doesn't

components.huge-component-to-be-rendered-async
contain a var name?

alex14:02:04

Thanks for pointing that out. I transcribed a poor example. Should say (lazy/lazy-component components.huge-component-to-be-rendered-async/the-component)

alex14:02:12

and the corresponding clj-kondo warning is

[unresolved-namespace] Unresolved namespace components.huge-component-to-be-rendered-async. Are you missing a require?

borkdude14:02:42

and lazy-component is something like requiring-resolve?

borkdude14:02:21

do you use this call on the top level in your namespace?

borkdude14:02:22

you can add components.huge-component-to-be-rendered-async to the :exclude-ed namespace of the unresolved namespace linter perhaps, or try this (experimental):

:config-in-call {your-ns/lazy-component {:linters {:unresolved-namespace {:level :off}}}}

alex15:02:45

> and lazy-component is something like `requiring-resolve`? Under the hood, it turns the var/symbol into a https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/lazy.clj#L22 and then invoked either https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/lazy.cljs#L31. I wasn't familiar with requiring-resolve, but I think the spirit is the same > do you use this call on the top level in your namespace? Yep, I define it at the top-level of the namespace via (def my-lazy-component (lazy/lazy-component components.huge-component-to-be-rendered-async)) > you can add `components.huge-component-to-be-rendered-async` to the `:exclude`-ed namespace of the unresolved namespace linter perhaps, or try this (experimental): I'll give this experimental route a try. Otherwise I'd have to add 20 or so namespaces to the exclusion list

alex15:02:44

>

:config-in-call {your-ns/lazy-component {:linters {:unresolved-namespace {:level :off}}}}
Seems to be working well, thank you!

borkdude15:02:49

great to hear :)

tomd11:02:40

I'd like to point flycheck-clj-kondo to a different config directory than the default (`.clj-kondo` in the root of the repo). Looks like there is a macro flycheck-clj-kondo-define-checkers but looks like it is already called at the bottom the package. Can I just call it again in my init.el with a --config-dir extra arg? I tried but it I think flycheck blew up.

1
tomd11:02:54

looks like I couldn't give it a ~ in the path. all fixed 🙂

Max15:02:41

Is there a way to add custom checks to clj-kondo? Say for example something simple like “don't import this library except in a specific ns”. I'm thinking about ways to guide devs towards the architectural happy path in a big project

borkdude15:02:17

@max.r.rothman Custom checks can be added via hooks, but I don't think hooks currently work on the ns macro. You can however check all required libraries and where they are required in the analysis output and do your own validation on that. There is also an undocumented :custom-lint-fn which you can apply to all the analysis so you get the same kind of warning output you'd normally get. You can only use that on the JVM, not via the binary.

Max15:02:39

Thanks for the info! Out of curiosity do you know whether Clojure LSP uses the binary version? Editor integration would be a plus

borkdude15:02:54

yeah, but the custom-lint-fn isn't in anyway pluggable, with JVM I meant, as a JVM library, not as a command line tool. for editor integration I think we could either make a proper linter about what namespaces are allowed to be used in which namespaces or make the ns form "hook-able"

borkdude15:02:57

e.g.:

{:forbidden-require {foo.bar [foo.baz]}}
or so

borkdude15:02:23

I recommend using the analysis output first so you get a taste of this and then we can propose a linter for this

Max16:02:56

I’m less concerned with this specific example and more interested in general with being able to write custom lint checks that enforce project-specific constraints. Sounds like that’s currently possibleish but it’s not a super well-paved path?

Max16:02:32

How likely do you think clj-kondo is to develop in any of these directions in the future? • Making ns hookable • Improving support/docs/guard rails for custom checks

borkdude16:02:03

The data is all there, that is super well paved. You just have to write a small program which checks this data.

borkdude16:02:48

Custom checks are already well supported, just not in this specific example. There more you are willing to cooperate and provide input/feedback, the more likely this will become.

Max16:02:32

By “a program which checks this data” do you mean a hook or a separate program that operates on analysis data?

borkdude16:02:08

Currently, the latter. In the future, we could make a new hook for this which just gets to see the namespace requires, imports, etc and then you can do your own pass over that.

borkdude16:02:41

I think that hook could get the raw ns form + the parsed ns form as arguments which should make it easier to assert something

borkdude16:02:20

Just takes some work and hammock time to do this. Needs an issue. Perhaps if you're company has funds, sponsoring this could also help.

Max16:02:24

Ah ok, I think I understand. Yeah probably all of the custom checks I’ve thought about can be implemented off of analysis data. I think the gap is integrating those custom checks into developer workflows, especially editors. Definitely as I get closer to actually building something I will file issues/make PRs/sponsor as appropriate, I’m currently just in the fact-finding phase

borkdude16:02:09

yeah, the editor integration will happen once you can make a hook that gets to see this data. see https://github.com/clj-kondo/clj-kondo/blob/master/doc/hooks.md

borkdude16:02:26

would it also help if you had a hook which only got to see the raw ns form? this can be very easily added

borkdude16:02:40

but the parsing of the ns form is a bit tedious and clj-kondo already does all of this

borkdude16:02:07

but we could definitely start with the raw form and add more things to it later

Max16:02:15

Are there other things that clj-kondo or analysis data are capable of doing that hooks are not?

Max16:02:05

If it helps to make things concrete other examples of potential custom checks might include stuff like • don’t call that fn without wrapping it in a try • all the fns in this ns should contain a call to this other fn • Other kinds of “please use this correctly” guard rails

borkdude16:02:12

The first one, clj-kondo is able to do this (it doesn't currently) but looking at the parent calls, these aren't exposed to hooks currently.

borkdude16:02:32

The second one you could do with hooks, by just looking at the arguments

borkdude16:02:17

hooks are mainly there to transform custom macros into known constructs, but slowly but surely extra things will be added to it.

Max16:02:36

Ok, so if I’m going to summarize this particular fact finding mission, it sounds like the conclusion is: • clj-kondo definitely can support at least some (and probably many) custom rules via hooks • certain checks might require new development in clj-kondo, which should be supported appropriately • Probably anything you could want in a custom check is supported by operating on analysis data, but that approach won’t integrate well into developer workflows Does that sound correct?

Max16:02:49

Awesome, thank you! This was really helpful

Max16:02:16

…I guess I actually have one more question: would there be interest in clj-kondo growing a way to have integrated custom checks that run in the integrated SCI interpreter and operate on arbitrary analysis data? I think that’s different from a hook, but maybe it’s a special kind of hook? Does this question even make sense? The observation here is that analysis data can do anything but doesn’t integrate well into dev workflows, so I wonder if there’s a way to bridge that gap

borkdude16:02:23

@max.r.rothman That's already what's happening, SCI is used to execute hooks. And yes, that's what I had in mind with exposing the parsed ns form as data in a hook.

Max16:02:40

Got it. Makes sense, thank you!

borkdude16:02:19

The general format with hooks is map in, map out and node is just one part of the map in/output. There can be many more things added over time.

gratitude-thank-you 1
borkdude13:02:38

@max.r.rothman Hmm, it seems hooks are available on namespace forms already: https://twitter.com/FourPillTherapy/status/1492831326680059911 So I guess you can already do a lot of what you want, using the raw ns form node.

Sam Ritchie15:02:23

Hey all! I am seeing an issue with clj-kondo where, in a cljc file, kondo does not peer inside the :clj branch of my form:

Sam Ritchie15:02:38

This triggers an unused binding warning on clj-kondo v2022.02.09.

borkdude15:02:35

This has always been the behavior. The binding is unused in the cljs form, so the warning applies to the cljs behavior.

borkdude15:02:50

You can do #?(:cljs _f :clj f) to fix this warning

Sam Ritchie15:02:44

nice, thank you!

Sam Ritchie15:02:04

do you like _f vs #?(:cljs _ :clj f) so that the symbol is at least sort of self documenting if you want to use it later?

borkdude15:02:13

no preference

Sam Ritchie15:02:18

fixing these warnings is addictive…

mkvlr11:02:07

is there a way to opt out of this behavior? Making the code more complex by introducing another reader conditional just to fix the unused var warning doesn’t seem right to me. I don’t see the benefit of having this be a warning or am I missing something? Since _ isn’t treated special in Clojure there’s zero difference in performance or behaviour, right?

borkdude11:02:20

An unused binding in one target, but not in the other target is usually a sign that you forgot something in handling the other target. This behavior has been in clj-kondo since the very start and intentional.

borkdude11:02:31

There is no option to opt out of it.

borkdude11:02:27

I think the first step should be to add the target to the warning message: unused binding [cljs] or so. since it sometimes confuses people why the binding was unused if it's used e.g. only in clj but not in cljs

borkdude11:02:41

there is already an issue for this

mkvlr11:02:22

but you think it’s useful in general? Looking at the code holistically (cljc) it’s not unused.

mkvlr11:02:44

I’ll comment on the issue

borkdude11:02:26

clj-kondo views .cljc files as actually two (or more) files: one for each platform and lints those separately. which issue?

borkdude11:02:57

I'm not entirely convinced that adding a config to suppress stuff based on one target is a good idea.

borkdude11:02:22

E.g. if you have an invalid arity call in .clj, but not in .cljs, would you not emit it because it's valid in just one target?

mkvlr12:02:35

I’d argue that it shouldn’t be a warning, but only this specific case, unused var. Feels like the game of pleasing the linter, without an actual improvement to how my code behaves.

mkvlr12:02:08

invalid arity is very different in this regard.

borkdude12:02:38

As I said, it's often an oversight when a binding is used in only one target, so I don't really agree with that it's only pleasing the linter. Can be, doesn't have to be, as always with linting.

1
borkdude12:02:14

I personally wouldn't use this option, if it existed.

mkvlr12:02:00

> Can be, doesn’t have to be, as always with linting. Surely this doesn’t apply to everything condo reports? A lot of things are almost certainly problems?

borkdude12:02:31

An unused binding isn't a problem to begin with.

borkdude12:02:00

You can also just disable it in a .cljc file if it's too much

Sam Ritchie17:02:20

question - when I am developing a custom config to export from a library… should the config I’m adding in resources be automatically picked up? Or do I have to add it to my config.edn in the library root

borkdude17:02:11

@sritchie09 You'll have to add it to config.edn in the root via :config-paths ["../resources/clj-kondo.exports/foo/bar"]

borkdude17:02:26

and then you can develop the exported config in resources to avoid duplication

Sam Ritchie17:02:01

{:lint-as {sicmutils.util.def/import-def potemkin.namespaces/import-def}} does seem to be having trouble, even though clj-kondo seems to know about potemkin just fine

Sam Ritchie17:02:11

maybe I need to use some other way of importing the config?

borkdude17:02:57

import-def might not be supported, import-vars is

borkdude17:02:00

brb, dinner

Sam Ritchie18:02:11

for when you are back… is it important, when writing a hook that expands from, say,

(with-literal-functions [x y] ...)
to
(let [x (literal-function 'x) y (literal-function 'y)] ...)
to generate the full expansion? or is
(let [x nil y nil] ...)
sufficient?

borkdude18:02:45

It depends, expanding to the latter might result in unused literal function var warnings, but depending on the context, that may not matter

Stan Foley18:02:39

I get a compiler error with the project that I cloned, and I was told that the question might be more relevant to this room. Can someone have a look please?

borkdude18:02:42

Can you explain how a compiler error relates to #clj-kondo?

Stan Foley19:02:28

I am new,and I was in another room. I was told that this room is more relevant to speclj

Stan Foley19:02:03

I know nothing about clj-kondo. I was told that speclj is related to it. If that is wrong I'll jump out, and I appologize for troubling you

Stan Foley19:02:09

Oh they told me that the linter complains about :refer :all and this room might know more about it

Stan Foley19:02:38

I was able to run it. Thank you and sorry for borthering you for nothing. I am reading the docs.

borkdude19:02:46

@U032K6B0EEA Ah I see. It's about the unresolved symbols. Normally those should go away when your dependencies are linted. Are you using Calva?

borkdude19:02:32

I see the issue. So the solution is to open each sub-project in the right root, where the project.clj is. Then #lsp will index the dependencies and the :refer :all is understood.

Stan Foley02:02:38

Hi. yes I am using Calva, and ok I'll open the projects separately. Thank you so much for the reply. Calva is just amazing, and so is Clojure. They make short work of some very useful functionalities.

Sam Ritchie19:02:55

so close to killing all warnings. A HAIRY macro, I think, is this one: https://github.com/sicmutils/sicmutils/blob/main/src/pattern/rule.cljc#L92-L101 which basically expands to a call to compile-pattern here: https://github.com/sicmutils/sicmutils/blob/main/src/pattern/syntax.cljc#L208 which does selective quoting and rewriting of the form. Luckily this is a small namespace, so I can copy the whole thing into the hook if I need to, AND rewrite it to operate on the clj-kondo internal representation. Is there instead some way to get the hook to run that particular namespace?

borkdude20:02:05

I don't really know what to make of this unless I also see example calls

Sam Ritchie20:02:09

sorry, pitiful bug report!

Sam Ritchie20:02:16

@U04V15CAJ SO, what the internal compile-pattern does is scans its incoming forms and handles various cases like:

- `(? x) => (list '? 'x)`
  - `(?? x) => (list '?? 'x)`
  - `($$ x) => (list '$$ 'x)`
  - any unquoted symbol is quoted
  - Any form unquoted like `~x` is left UNquoted
  - Any form marked `~@(1 2 3)` is spliced in directly

Sam Ritchie20:02:53

that requires walking the incoming syntax tree and performing various substitutions, dropping down recursively into any sequences or maps

borkdude20:02:17

but what linting errors does it actually cause? those might be easy to prevent regardless of what the macro does

Sam Ritchie20:02:02

yup, writing this up is helping me realize that there might be a simpler way. The main error is “unresolved symbol”;

borkdude20:02:21

do you have an actual example call?

Sam Ritchie20:02:31

yes, getting it together now

👍 1
Sam Ritchie20:02:35

here is a 1-nested pattern:

Sam Ritchie20:02:56

so, yes… I think I am seeing the simpler form

Sam Ritchie20:02:05

any symbol should NOT ever trigger an unresolved symbol error;

Sam Ritchie20:02:11

unless it is inside a splice form like

Sam Ritchie20:02:02

so what I’m realizing (duh) is that it might be that a much simpler walker that just quotes everything except stuff inside splices or unquote-splices will do it

borkdude20:02:58

what you could do is write a hook that ignores every question marked symbol, or just ignore all unresolved symbol errors whatsoever by configuring :linters {unresolved-symbol {:exclude [(your-ns/pattern)]}}

borkdude20:02:48

the latter is the quick solution

Sam Ritchie20:02:21

yup, but in this case I DO want to have the unresolved symbol error on expected in that last example

Sam Ritchie20:02:54

@U04V15CAJ I think I haave the answer so don’t let me take your time here

borkdude20:02:55

what you can do is walk the body, select all unquoted expressions and then return a vector node of those

Sam Ritchie20:02:31

oh, I like that

borkdude20:02:45

I have a similar macro in babashka.process, called $ which also uses unquote in this way

Sam Ritchie20:02:32

so by returning the vector, everything in the vector will get hit with the linter

Sam Ritchie20:02:13

now that I’m started on this, it’s super fun writing hooks etc

🙂 1
Sam Ritchie19:02:49

second Q, so not threading… I am getting warnings from data_readers.clj. is there a way to turn these off… OR is it correct that I should be calling require in my data_readers file?

src/data_readers.cljc:1:2: warning: Unresolved namespace sicm. Are you missing a require?
src/data_readers.cljc:1:18: warning: Unresolved namespace sicmutils.util. Are you missing a require?
src/data_readers.cljc:2:18: warning: Unresolved namespace sicmutils.ratio. Are you missing a require?
src/data_readers.cljc:3:18: warning: Unresolved namespace sicmutils.complex. Are you missing a require?
src/data_readers.cljc:4:18: warning: Unresolved namespace sicmutils.quaternion. Are you missing a require?

borkdude20:02:37

Hm no, this should be fixed in clj-kondo. issue welcome

borkdude20:02:19

Oh, there is already an issue for it: https://github.com/clj-kondo/clj-kondo/issues/892#issuecomment-803461457 I will bump priority

Sam Ritchie21:02:19

here is a potential bug:

Sam Ritchie21:02:36

this is a reader literal defined as:

Sam Ritchie21:02:46

(defn parse-bigint [x]
  `(bigint ~x))

Sam Ritchie21:02:23

so the macroexpansion checks out fine, but not the reader-literal wrapper

Alex Miller (Clojure team)21:02:53

if you're expecting #sicm/bigint one-e-40 to eval that, that is not how reader literals work

Alex Miller (Clojure team)21:02:07

but I'm not sure what you think is the bug here

Sam Ritchie21:02:36

@alexmiller my thought was that reader literals are only supposed to do a minimal code transform, NOT to actually evaluate to a bigint

Sam Ritchie21:02:38

(in this case)

Alex Miller (Clojure team)21:02:08

can you post it w/o the images - I literally can't see all of that stuff at the same time

Alex Miller (Clojure team)21:02:53

reader literals happen at read time so the reader literal function is passed unevaluated values

Sam Ritchie21:02:17

;; I'm using this reader literal because I need cljs to produce a `js/BigInt`,
;; and there is no `bigint` function that will do that for me:
(let [one-e-40 (apply str "1" (repeat 40 "0"))]
  #sicm/bigint one-e-40)

Sam Ritchie21:02:44

@alexmiller yes, so for clojurescript I need to emit (js/BigInt form) for #sicm/bigint form

Sam Ritchie21:02:11

but for clj I could of course just emit a BigInt instance, since I’m on the JVM

Sam Ritchie21:02:53

(maybe the cljs angle explains why I am emitting (u/bigint form) , and in turn why I was expecting #sicm/bigint one-e-40 to work

Alex Miller (Clojure team)21:02:01

I know this has been an area of discussion in cljs lately, not sure what current state is, but might need to check in #cljs-dev

Alex Miller (Clojure team)21:02:33

certainly for Clojure, this looks all wrong, but I don't know what to expect with cljs right now

Sam Ritchie21:02:44

It's weird, but it DOES work - and I did it to make behavior the same one both sides

Sam Ritchie21:02:50

But yeah worth digging

borkdude21:02:02

@alexmiller That's what I thought too, but when another user brought this up recently, and I tested it, I thought clj-kondo was wrong: https://github.com/clj-kondo/clj-kondo/issues/1579

borkdude21:02:23

Feel free to chime in in that issue!

borkdude21:02:41

I made a fix regarding data literals when someone has #foo ([dude]) where clj-kondo linted it incorrectly as a vector being called as a function. After that fix, the expression is basically handled as a quoted expression. But apparently this is also not how it works.

Alex Miller (Clojure team)21:02:12

I know Henry Widd has been advocating for changes in this area for a while to get the ability to do platform specific reader literal stuff (https://clojure.atlassian.net/browse/CLJS-3294 at least), not sure what the outcome of all that was in CLJS

borkdude21:02:07

What does that have to do with the above issue?

borkdude21:02:21

I tested the above on JVM Clojure

Alex Miller (Clojure team)21:02:42

I don't know exactly, I just know that he's been trying to make cross platform data readers work

borkdude21:02:34

@sritchie09 Let's go into a thread. This is a repro I tried on the JVM:

(set! *data-readers*
      (assoc *data-readers* 'time/date identity))

(let [foo "2022-02-10"
      bar #time/date foo]
  (prn bar))

Sam Ritchie21:02:39

Nice, way tighter . I'm away from keyboard now - does it show the same behavior?

borkdude21:02:11

even:

(def foo 1)
(let [bar #time/date foo]
  (prn bar))
returns 1

Sam Ritchie21:02:21

But does clj-kondo Think that foo is unused in this case?

Alex Miller (Clojure team)21:02:15

that that prints 1 instead of foo is I think an accident of implementation and where evaluation happens

Alex Miller (Clojure team)21:02:50

I guess I'd expect bar to be bound to the symbol foo

borkdude21:02:31

> But does clj-kondo Think that foo is unused in this case? Before the latest release it it didn't, but because of a fix for this issue: https://github.com/clj-kondo/clj-kondo/issues/1563 it now basically treats the "data" as a quoted expression.

Alex Miller (Clojure team)21:02:06

imo, that is correct in CLJ

Sam Ritchie21:02:41

I think I disagree because of what happens when you put an explicit quote in the argument to a reader literal

Alex Miller (Clojure team)21:02:57

the #time/date fn is called during read with unevaluated values

Sam Ritchie21:02:10

I’ll pick back up when I’m not a distracted passenger in the car, thanks to you both for looking at this!

Sam Ritchie21:02:07

But the return value is treated like the return value from a macro

Alex Miller (Clojure team)21:02:29

if so, that is accidental implementation in CLJ (but I think is expected in CLJS)

Alex Miller (Clojure team)21:02:34

this is where the platforms differ

borkdude21:02:22

I'll just relax the linting in clj-kondo in accordance with the accidental implementation, since people seem to rely on this....

Alex Miller (Clojure team)21:02:35

well, they shouldn't and they could be broken in the future

Sam Ritchie21:02:50

Alex, the current implementation matches how the vector literal and map, set etc work right?

Alex Miller (Clojure team)21:02:18

those are different - literal colls evaluate their elements

Sam Ritchie21:02:50

#sicm/quaternion [a b ‘x ‘y] was a place where I was relying on this

Sam Ritchie21:02:04

First two elements were let bound, the third and fourth are explicit quoted symbols

Sam Ritchie21:02:14

(Since I can do symbolic arithmetic)

Alex Miller (Clojure team)21:02:22

https://clojure.org/reference/evaluation "Vectors, Sets and Maps yield vectors and (hash) sets and maps whose contents are the evaluated values of the objects they contain."

Sam Ritchie21:02:07

Totally, and evaluation in this case makes bar bind to 1 like it does now

Sam Ritchie21:02:17

Whereas Unevaluated would be the symbol foo

Alex Miller (Clojure team)21:02:17

https://clojure.org/reference/reader#tagged_literals "The data reader function is invoked on the form AFTER it has been read as a normal Clojure data structure by the reader." (notably no evaluation mentioned here)

Alex Miller (Clojure team)21:02:57

so you're talking about the combination of these

Sam Ritchie21:02:14

yes, so the symbol (unevaluated) is passed in, and then the returned form is evaluated. Which matches my mental model that [] expands to a call to “vector”

Alex Miller (Clojure team)21:02:02

not a great model but maybe ok

Sam Ritchie21:02:35

okay, got a keyboard for a moment

Alex Miller (Clojure team)21:02:40

I guess the combination of the things above would imply evaluation of the literal vector at read time before being passed to the reader fn

Alex Miller (Clojure team)21:02:26

I would say this is certainly not the intent of the reader fn, which is to take primitive data values to describe new complex data values

Sam Ritchie21:02:56

@alexmiller hmm, it is quite useful for sure to be able to make compound values too

Alex Miller (Clojure team)21:02:07

that's what functions are for

Alex Miller (Clojure team)21:02:35

I can't stick it in an edn file and read it elsewhere

Sam Ritchie21:02:45

the reason I was doing all this was so I could have a printable representation that someone could evaluate

Sam Ritchie21:02:07

so quaternions are 4-vectors, but they print like #sicm/quaternion [r i j k]

Alex Miller (Clojure team)21:02:25

it's the evaluation in the constructor that's fishy

Sam Ritchie21:02:27

which you can then paste in; which works with primitives, I see what you’re saying

Sam Ritchie21:02:36

I think I am explicitly NOT evaluating in the constructor

Alex Miller (Clojure team)21:02:57

what are a and b here then? #sicm/quaternion [a b 'x 'y]

Sam Ritchie21:02:18

say, 1 and 2; but the emitted form from that is just (quaternion a b 'x 'y)

Alex Miller (Clojure team)21:02:41

I'd say use the function in that case

Sam Ritchie21:02:46

so reader literals should always emit data;

Sam Ritchie21:02:59

and it’s an accident now that they can emit code forms, like a macro?

Alex Miller (Clojure team)21:02:08

it's an accident that that "works"

Sam Ritchie21:02:17

I totally get it, we are clashing on my model of [a b c] expanding to (vector a b c)

Sam Ritchie21:02:31

which works, even when a b and c are bound outside of [a b c]

Alex Miller (Clojure team)21:02:34

if you want a macro, make a macro

Sam Ritchie21:02:23

okay, thanks for talking it out

Sam Ritchie21:02:41

it might be an accident, but it is a nice feature 🙂

Alex Miller (Clojure team)21:02:41

you should be able to take the thing that is a data reader and read it in another jvm and get a value

Alex Miller (Clojure team)21:02:51

it's bad and you should feel bad :)

🙏 1
😂 1
Alex Miller (Clojure team)21:02:22

it's basically reader macros, which are intentionally not a feature in Clojure

Alex Miller (Clojure team)21:02:42

and this has come up before in cases where it didn't work like someone expected (don't remember the context now)

Alex Miller (Clojure team)21:02:49

but it was something Henry was doing

borkdude22:02:24

So, is the summary: it works (similar to macro-expansion), but it's not how it's intended to be used in JVM Clojure, but in CLJS it is explicitly supported?

Alex Miller (Clojure team)22:02:59

yes for the CLJ parts, I think for the CLJS parts but I would verify that with dnolen

borkdude22:02:51

So the gist is: data readers should be used to read context-free data.

Alex Miller (Clojure team)22:02:32

or reader literals are literals

borkdude22:02:33

So the current take of clj-kondo on this aligns with how it's intended (at least on the JVM)

henryw37410:02:46

feel my ears burning 🙂 to explain what the change is that I was advocating for (and has recently been merged to cljs master). A data_readers.cljc file with the following content: {foo/bar #?(:clj xx/yy :cljs ff/gg)} is read by the clojure compiler ok (and it reads the :clj branch). The cljs compiler did not have conditional read enabled so would barf on that. With the change, the cljs compiler reads the :cljs branch. That's it really - so no change to how data reader functions work

borkdude10:02:21

ok. I wonder how clj-kondo could help with promoting the idea of reading literals instead of expressions that depend on context, but this depends on what the reader function does and clj-kondo cannot know this

henryw37410:02:12

hmm, probably I'm being naive but in #foo/bar a it must see that a is not a literal

borkdude10:02:06

no! the point is that a could be a literal and should be handled as a literal by the reader function, but often is not, because people are abusing reader functions as macros

borkdude10:02:02

Hopefully I'm paraphrasing correctly, what Alex explained in this thread

henryw37410:02:42

ok. I can't think I would write something like #foo/bar a in code. I would think it better style to just call the function. again repeating what's been said above, but reader functions targeting cljs (written in clojure) are often necessarily macro-like (as per one emitting

(js/BigInt ~x)). 
mentioned somewhere in the thread)

borkdude11:02:34

Why is that necessary (instead of just calling a function or macro)?

borkdude11:02:43

And how is that unique to CLJS?

borkdude11:02:36

because the reader runs on the JVM but the code produced runs in JS?

henryw37411:02:57

Yes, exactly that

henryw37411:02:47

But note a reader function intended to run in a js env, such as when reading edn, will not be macro like

borkdude11:02:43

so when do you need a reader function for this rather than a macro? as in, the necessary part?

borkdude11:02:06

or even a function

henryw37411:02:23

for me personally I use cljs data literals in the repl. the tags round trip via printer. so e.g. calling some fn that prints out #foo/bar "abc" , then I'll want to paste that back in. I don't think I would ever put a tagged literal in a source file

henryw37411:02:19

for one thing, in clojure, you have to make sure the reader function exists already, or else the reader will complain. iow you have to require the reader function ns in any ns where you use the literal. so if you have to do that, you might as well call the fn directly imo

borkdude11:02:49

what is the point with the last issue?

borkdude11:02:21

I don't see how it relates to "read only literals, not context-sensitive expressions", could you explain?

henryw37411:02:18

well my feeling is the context sensitive thing would only come up in source code. and ofc that's what kondo is for 🙂 . I guess I'm saying if kondo complained error: tagged literal in source code then I would be happy

borkdude11:02:16

isn't that the main idea behind data_readers.cljc though, that you can use it in source code? ;)

henryw37412:02:34

well I only want to use when interacting with the REPL. I appreciate others may feel differently. and I would be interested to hear arguments for having them in source code. I mean, there's usually little in it verbosity-wise and tagged literals intentionally add indirection-- but I don't want that indirection in source

Sam Ritchie14:02:09

@U051B9FU1 one spot I am abusing this is to get ratio support in Clojurescript; I do have #sicm/ratio 1/2 forms in my source that can actually product (rationalize (bigint 1) (bigint 2)) in cljs because, by running on the JVM side, the 1/2 reader literal actually makes sense

Sam Ritchie14:02:36

that’s the only spot where it would be aesthetically blah to switch to (ratio 1 2) all over (though (/ 1 2) when I have my overriding / imported, so even that is not so bad

henryw37420:02:02

thanks @sritchie09 - personally I'd go with the fn call style you mention but appreciate that's personal preference. Slight digression but I certainly wouldn't put them in library source as I think end user should have full control over reading and printing. but would you do #sicm/ratio foo though if it was written in macro style? & back to clj-kondo and discouraging macro-style reader functions @U04V15CAJ I still think kondo could report on usage such as

#foo/bar a 
I see that given:
(let [x "2022"]
  #inst x)
kondo reports warning: unused binding x . this particular code doesn't compile with the default Clojure/script binding of #inst , because the reader fn is not in macro-style, but kondo can't know that I guess, but as per my suggestion it might say don't pass variable to tagged literal, just call the fn/macro directly

borkdude20:02:04

clj-kondo cannot know if you're passing x as a contextual value or as a literal. the reader function always sees the literal symbol x - it's up to that function what to do with it and this is opaque to clj-kondo.

borkdude20:02:34

so x might be really unused, depending on what the reader function expands into

borkdude20:02:49

so clj-kondo cannot reliably warn against something here

henryw37421:02:00

Ok, I'm sure you're right!

borkdude21:02:17

@U051B9FU1 it's the same with:

(defmacro foo [x] ...)
The macro could be doing:
(list 'quote x)
So then (let [g 1] (foo g)) here g is unused, even though its name was passed to the macro.

borkdude21:02:38

symbols are data and data reader function could treat those just as literal symbols, which makes things almost impossible to statically analyze, unless clj-kondo had a config for data reader function expansion like it has for macros, I guess it could add that, but not sure how useful it would be

henryw37421:02:52

I'm struggling to think why you might pass a valid variable symbol to a reader function that would treat it just as a symbol.

henryw37421:02:28

But who knows

borkdude22:02:23

well, given examples like:

#garden.types.CSSAtRule {:identifier :media
                             :value {:media-queries {:min-width "768px"}
                                     :rules ([".md\\:container" {:max-width "768px"}])}}
(note: :rules is not evaluated, else the vector would be alled as a function with no args, resulting in a read time error) it might not be far fetched that there can be random symbols in chunks like that, e.g. :id foobar

borkdude22:02:50

I think the best clj-kondo can do here, might be to not emit any warnings on code that could be valid Clojure, getting out of the way

henryw37418:02:54

yeah makes sense. :thumbsup:

Sam Ritchie21:02:41

Actually, @alexmiller what was weird about my example in your mind?

Alex Miller (Clojure team)21:02:46

it looked like you were expecting one-e-40 to be evaluated when I expect it (in CLJ) to just be passed as a symbol to the reader literal function. and reader literal functions should return a value, not an expansion.

borkdude21:02:59

Can we continue in the thread above, to reduce noise for others not interested in this?

zane21:02:01

What determines the Clojure version clj-kondo lints against? It doesn’t appear to be the one in deps.edn.

❯ clj
Clojure 1.10.3
user=> abs
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: abs in this context
❯ clj-kondo --lint src
src/….clj:8:1: warning: abs already refers to #'clojure.core/abs
linting took 594ms, errors: 0, warnings: 1

borkdude22:02:03

@zane clj-kondo's built-in cache has a recent version of clojure (alpha3, I realize it needs bumping), but if you lint your deps, including your used clojure, then you get what you're really using.

zane22:02:24

@U04V15CAJ How do I go about linting my deps, including my used clojure? I’m launching it from the same directory as the deps.edn and have tried --lint . and --lint deps.edn ….

borkdude22:02:44

@zane clj-kondo --lint $(clojure -Spath) --dependencies

borkdude22:02:14

The --dependencies flag will take care of a few things: 1. it won't emit any warnings 2. it will not re-lint jar files it's already seen

zane22:02:47

Thanks!

👍 1
zane22:02:21

Would you recommend --dependencies in CI, then?

borkdude22:02:35

For better linting results, it does provide benefits, e.g. you will see invalid arity calls to third party deps, but it's also totally fine to not do this.

borkdude22:02:25

you can cache the linting results though, if you're deps.edn didn't change

borkdude22:02:35

and then the second time should be faster, if that's a concern