This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-08-02
Channels
- # announcements (6)
- # beginners (35)
- # calva (5)
- # cider (3)
- # clj-http (1)
- # clj-kondo (52)
- # clojure (51)
- # clojure-conj (2)
- # clojure-europe (28)
- # clojure-losangeles (1)
- # clojure-norway (8)
- # clojure-uk (2)
- # cursive (12)
- # datalevin (2)
- # datomic (38)
- # emacs (7)
- # events (5)
- # gratitude (1)
- # humbleui (7)
- # hyperfiddle (23)
- # jobs (1)
- # off-topic (6)
- # portland-or (1)
- # rdf (3)
- # releases (2)
- # xtdb (3)
Working on this issue now: https://github.com/clj-kondo/clj-kondo/issues/916
tl;dr: you should not refer to a destructured value in the same map, inside of an :or
because the order of a map is undefined
example:
{:keys [a b] :or {a b}}
usually this code works, but it's by accident.
if we swap a and b in :keys
it already falls apart:
user=> (let [[{:keys [b a] :or {b (inc a)}}] {}])
Syntax error compiling at (REPL:1:28).
Unable to resolve symbol: a in this context
While implementing the linter I found this case: https://github.com/clojure/clojurescript/blob/47a7bfdefd156027b7b98cb483ddd93add71fbb2/src/main/clojure/cljs/repl.cljc#L1051
Not only does this example use a binding in :or
from the same destructuring map, the value is even more undeterministic since it depends on a default in the same :or
map
the similarities being that in one case it resolves to a var, the other it resolves to a local depending on the order of your map.
your example is supposed to work I think, due to how destructuring expands, flush
is only ever defined as a local once and there's a get
expression that gets the :or
fallback
that case is actually explicitly supported here: https://github.com/clj-kondo/clj-kondo/blob/c7b00f056fb79ed7207f67a67e4c34b718a41c24/src/clj_kondo/impl/analyzer.clj#L121
https://github.com/clj-kondo/clj-kondo/issues/915 see also Alex's comment in there
Yeah I see now how it can never be resolved to a local with the current impl. FWIW I think with the license Alex gives in the first sentence this is a valid implementation of destructuring (which the default resolves to a local).
(let [{:keys [a] :or {a a}} {}] a)
=>
(let [_m {}
_not-found (Object.)
a (clojure.core/get _m :a _not-found)
_default a
a (if (identical? a _not-found)
_default
a)]
a)
personally I would disallow this in Typed Clojure, but clj-kondo is a different beast and I think you're the best person to decide.
I think the above thing is just one special case of "a clojure map is unordered" and that's why certain things can go wrong in map destructuring combinations, that's what the check is about. So it very much checks against the current implementation and what can go wrong with that, not against a standard (because there isn't one). Your alternative impl of destructuring would come with yet other problems, clj-kondo can be adapted if clojure might change to a different implementation
I'm with you, I think it's a judgement call. For example, you decided in the OP you need to disallow the first form because swapping the vector would change the semantics. But given that small maps are ordered, in some ways it's deterministic AFAICT. But since it's error-prone, I think we both agree that it should be picked up.
This one is also pretty interesting:
((fn [{:keys [a b c] :foo/keys [d e f] :or {b (inc f)}}] [a b c d e f]) {:a 42 :foo/f 13})
here's one that will break your brain I found at work today. is this wrong? (plumatic schema)
(s/defn a [{:keys [a :- s/Num]}] ...)
now let's wait for someone to abuse this to make a kinda typed clojure with existing syntax
I'm looking for a better name (if possible) for:
:destructured-or-refers-bindings-of-same-map
Perhaps :ambiguous-keys-destructuring
or :nondeterministic-keys-destructuring
? Those are less specific, for better or worse.
Another alternative: :nondeterministic-keys-or-destructuring
The nice thing about starting with :or
is that it matches what it is talking about and won't be misread as a boolean,