This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-12-13
Channels
- # adventofcode (36)
- # aleph (1)
- # announcements (7)
- # aws (4)
- # babashka (14)
- # beginners (61)
- # calva (79)
- # cider (19)
- # clojure (48)
- # clojure-austin (1)
- # clojure-australia (2)
- # clojure-czech (2)
- # clojure-europe (46)
- # clojure-france (8)
- # clojure-nl (19)
- # clojure-uk (4)
- # clojuredesign-podcast (14)
- # core-logic (42)
- # data-science (3)
- # datalevin (8)
- # datomic (76)
- # events (1)
- # figwheel-main (9)
- # fulcro (6)
- # helix (1)
- # holy-lambda (1)
- # honeysql (2)
- # jobs (2)
- # jobs-discuss (20)
- # leiningen (5)
- # lsp (87)
- # minecraft (11)
- # nextjournal (4)
- # off-topic (17)
- # practicalli (1)
- # reagent (22)
- # reitit (8)
- # releases (3)
- # rum (2)
- # shadow-cljs (18)
- # sql (11)
- # tools-build (5)
- # tools-deps (9)
- # xtdb (20)
^ This is what we ended up putting in our playbooks for guarding against the CVE. Works nicely!
when reading the clojure core specs I noticed this oddity works
(defn fn-with-attr-map
"Is a fine function"
{:some-meta-here true}
([a b]
(+ a b))
([a b c]
(+ a b c))
{:some-extra-meta true})
I've never seen it used but my reading of the Clojure code is limited 🙂 ... any happy users of this facility out there?
Oh I remember Alex Miller explaining rationale behind this before... Unfortunately, don't remember the explanation
Hah, that’s odd. At a glance, I thought that the extra stuff was lexically attached to one of the arities.
It's attached to the var, not the arities
It's allowed at the end for the case where you might have a big metadata map and don't want to obscure the arities
But it is very rarely used
@lee gave me this example from https://github.com/grammarly/omniconf/blob/588e9e9200509f7c6afe1ff5f2bddad7bab5b0a4/src/omniconf/core.clj#L342
Would it be reasonable to warn against using quoted test constants in case
for clj-kondo or is there a legitimate use case for it?
https://github.com/clj-kondo/clj-kondo/pull/1491#issuecomment-992576345
The following returns :quote-a
, as expected (but not everyone might expect that).
Another interesting bit, if you uncomment the #{a}
one, it seems the entire case is expanded into a condp =
and it fails with unable to resolve a
.
(prn (case 'quote
;; #{a} 4
;; a :a
'a :quote-a ;; duplicate
;; '[a] :foo
;; [a] :bar ;; duplicate
1))
IMO it’s reasonable to warn. It’s more likely to be an error on the programmer’s part than a legitimate need. If the programmer really wants to match against quote
, they can write it out (and IMO they should).
yes, given that 'a
and (quote a)
are the same, it makes sense to direct them to the expanded version
OK, the issue is implemented in clj-kondo: https://twitter.com/borkdude/status/1471936924483006475 by @U02QXKSHP97 (thanks!) I just wonder if the warning could become more clear to explain what the issue is. Currently users might still think: yeah, quoting, that's what I want dude, what are you complaining about.
Maybe we should emphasize: > The test-constants are not evaluated. They must be compile-time > literals, and need not be quoted.
So the message could become: "Test-constants are not evaluated and need not be quoted: 'a"
Asking in a separate message rather than the thread, so as not to drive that conversation away from borkdude's actual question: how in the world is that working? Why does 'quote
match the 'a
branch?
@dorandraco Because 'a
is read as (quote a)
This is why many people get away without knowing symbols need not be quoted, they are actually matching agains the symbol quote
as well
Oh! Because case interprets lists as alternatives, not as calls. That's super confusing.
clj-kondo can actually see if you wrote 'a
vs (quote a)
so it can warn appropriately on the first thing
The "unable to resolve" error with #{a}
is most puzzling for me, even though eg.`#{b}` works. Surely it doesn't turn into condp, it's supposed to be constant-time dispatch
It seems case
falls back to condp
when there are too may different types:
user=> (case '#{a} #{a} 1)
1
user=> (case '#{a} #{a} 1 :foo 2)
1
user=> (case '#{a} #{a} 1 :foo 2 'a 3)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: a in this context
or no, it contains a condp=.
user=> (macroexpand '(case '#{a} #{a} 1 :foo 2 'a 3))
(let* [G__168 (quote #{a})] (case* G__168 9 3 (throw (java.lang.IllegalArgumentException. (clojure.core/str "No matching clause: " G__168))) {1 [-1640525200 (clojure.core/condp clojure.core/= G__168 #{a} 1 a 3 (throw (java.lang.IllegalArgumentException. (clojure.core/str "No matching clause: " G__168))))], 2 [:foo 2], 3 [quote 3]} :compact :hash-equiv #{1}))
@borkdude case
uses condp
not when there are too many different types, but when hashes collide (and it has chosen the 'hash-equiv' strategy)
the pseudo code for it is:
x = (hash the case input argument)
jumpidx = (clojure.core/shift-mask shift mask x)
jump to that index in the bytecode table
make sure that the input and the table entry are =
user=> (#'clojure.core/shift-mask 9 3 (.hashCode '#{a}))
1
user=> (#'clojure.core/shift-mask 9 3 (.hashCode 'quote))
3
user=> (#'clojure.core/shift-mask 9 3 (.hashCode 'a))
1
user=> (#'clojure.core/shift-mask 9 3 (.hashCode :foo))
2
note that this is a totally fabricated example, not one from production code, but still
The 'a
is a bit confusing, because in any other context it would be a single value, but in a case
it becomes a pair.
But I agree with Michiel, the condp
isn’t quoting the values that it receives. It’s not just a missing quote on that set. It also affects the symbol a
:
user=> (case 'a #{a} 1 :foo 2 a 3)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: a in this context
user=> (def a nil)
#'user/a
user=> (case 'a #{a} 1 :foo 2 a 3)
Execution error (IllegalArgumentException) at user/eval158 (REPL:1).
No matching clause: a
If I hand-edit the code generated from the case
macro so that the compared values in the condp
are quoted, then it works as intended.I’ve put a suggested patch on https://ask.clojure.org/index.php/11395/hash-collisions-case-statements-try-evaluate-the-constants, since I think it has to go through there before it can go to Jira, right?
^"[Ljava.lang.String;"
Ljava.lang.String;
dorabs-imac:~ dorab$ clj
Clojure 1.10.3
user=> (type (into-array String []))
[Ljava.lang.String;
user=>
You need the quotes because of metadata rules, you need the [ because it’s an array, and you need the ; because it’s a reference type
Unrelated to @U0ELT5ZDE’s question, this is an interesting rabbithole to go down. That link above is for ClassNames in internal form for the JVM. That’s actually different to the form of BinaryNames described in the Java Language Specification. Quoting from the JVM spec:
> For historical reasons, the syntax of binary names that appear in `class` file structures differs from the syntax of binary names documented in JLS §13.1. In this internal form, the ASCII periods (`.`) that normally separate the identifiers which make up the binary name are replaced by ASCII forward slashes (`/`). The identifiers themselves must be unqualified names (https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2.2).
The Java Language Specification https://docs.oracle.com/javase/specs/jls/se17/html/jls-13.html#jls-13.1 is a bit harder to read than the JVM spec document, so it makes sense to see what they look like in the JVM spec, and just replace the /
characters with .
characters.
You can also get to these links by looking at the javadoc for https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getName(). This says that it will return the binary name of the class with a https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ClassLoader.html#binary-name. This describes class names with dot separators, and has links to the Java Language Specification (https://docs.oracle.com/javase/specs/jls/se17/html/jls-6.html#jls-6.7 and https://docs.oracle.com/javase/specs/jls/se17/html/jls-13.html#jls-13.1)
@U07MMB5JB close - you need the double quotes because otherwise it wouldn't be readable (unbalanced [
token and trailing ;
token break reader rules)