Given that (.getResource (var-get clojure.lang.Compiler/LOADER) "cognitect/test_runner.clj")
works, why wouldn't (require 'cognitect.test-runner)
given that the failure is FileNotFoundException Could not locate Clojure resource on classpath: cognitect/test_runner.clj clojure.lang.RT.loadResourceScript (RT.java:388)
. Disclaimer: I'm using a custom classloader.
Ah, clojure uses getResourceAsStream
instead of getResource
, I think the class loader is broken for AsStream with files. Bummer.
Would a ticket for improving this error message be welcome?
09:33 $ clj
Clojure 1.10.0
user=> (let [{:keys [foo]} 3] (println "foo"))
Syntax error (UnsupportedOperationException) compiling at (REPL:1:1).
Can't type hint a primitive local
user=>
Seems like ^ is a bug (it shouldn't fail at all)
what's slightly confusing is that there are no type hints in the user code, it's coming from destructuring macroexpansion
I'd be curious to see if this repro was distilled from a different scenario in the wild @slipset
@ghadi No, I was rewriting some code at work and made the mistake of trying to destructure a number. So apart from naming the key foo and trying to print it, this was code I actually wrote.
It comes from here:
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L6015
user=> *e
#error {
:cause "Can't type hint a primitive local"
:via
[{:type clojure.lang.Compiler$CompilerException
:message "Syntax error compiling at (1:1)."
:data #:clojure.error{:phase :compile-syntax-check, :line 1, :column 1, :source "NO_SOURCE_PATH"}
:at [clojure.lang.Compiler analyze "Compiler.java" 6808]}
{:type java.lang.UnsupportedOperationException
:message "Can't type hint a primitive local"
:at [clojure.lang.Compiler$LocalBindingExpr <init> "Compiler.java" 6015]}]
:trace
[[clojure.lang.Compiler$LocalBindingExpr <init> "Compiler.java" 6015]
[clojure.lang.Compiler analyzeSymbol "Compiler.java" 7297]
[clojure.lang.Compiler analyze "Compiler.java" 6768]
[clojure.lang.Compiler analyze "Compiler.java" 6745]
[clojure.lang.Compiler$InvokeExpr parse "Compiler.java" 3888]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7108]
[clojure.lang.Compiler analyze "Compiler.java" 6789]
[clojure.lang.Compiler analyze "Compiler.java" 6745]
[clojure.lang.Compiler$HostExpr$Parser parse "Compiler.java" 1020]
So the macroexpanded thing looks like:
user=> (clojure.pprint/pprint (macroexpand '(let [{:keys [foo]} 3] (println foo))))
(let*
[map__165
3
map__165
(if
(clojure.core/seq? map__165)
(clojure.lang.PersistentHashMap/create (clojure.core/seq map__165))
map__165)
foo
(clojure.core/get map__165 :foo)]
(println foo))
nil
user=>
Which at least seems to have a redundant call to seq
in the true
branch of the if
test.
Compare this with planck:
cljs.user=> (macroexpand '(let [{:keys [foo]} 3] (println foo)))
(let* [map__24 3
map__24 (if (cljs.core/implements? cljs.core/ISeq map__24) (cljs.core/apply cljs.core/hash-map map__24) map__24)
foo (cljs.core/get map__24 :foo)]
(println foo))
cljs.user=>
repro
user=> (let [x 1] (when (seq? x) (.first ^clojure.lang.ISeq x)))
Syntax error (UnsupportedOperationException) compiling at (REPL:1:27).
Can't type hint a primitive local
https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L4431
doesn't seem to me like that type hint is needed
Even if it is needed, wouldn’t it be safer to add it at the point where create
is called?
yeah it was needed when the macro expanded to (c.l.PHM/create gmapseq)
instead of (c.l.PHM/create (seq gmapseq))
it's useless right now
Ah, so we need to somehow coerce/typehint the gmap
local to get the correct version of c.l.PHM/create
?
no, the current implementation doesn't need it
a previous implementation did, and so the type hinting was in place, but then a call to seq
was introduced which rendered the type hint unnecessary but i guess it was just forgotten there
JIRA seems really slow. Not sure if there's something wrong, but for instance I am stuck at a loading screen here: https://clojure.atlassian.net/projects/TCLI/issues
so could we just do (c.l.PHM/create gmap)
(since we know from the if thest that gmap
is a seq
?
- gmapseq (with-meta gmap {:tag 'clojure.lang.ISeq})
defaults (:or b)]
(loop [ret (-> bvec (conj gmap) (conj v)
- (conj gmap) (conj `(if (seq? ~gmap) (clojure.lang.PersistentHashMap/create (seq ~gmapseq)) ~gmap))
+ (conj gmap) (conj `(if (seq? ~gmap) (clojure.lang.PersistentHashMap/create (seq ~gmap)) ~gmap))
we could do this now
@devn I think there are some 'issues' with it... :simple_smile:
yeah, i clicked a couple times and now it seems to load All Issues
also the SSO gets confused
I for some reason can't login from firefox just on linux
The issues page never loads (just spins) if you're logged into JIRA with a different account (or perhaps just not logged into your Clojure account?).
another possibility would be forbidding numbers on the right hand side of a map/vector destructuring
You can do other stuff in JIRA with projects but the issues page won't load for un-auth'd accounts @devn
with defmacro specs
agreed
No worries, that’s why I asked before creating an issue.
I guess my main point was that while happy it didn’t fail silently, I was a bit surprised by the error message, and since those have been a priority lately, I figured it would be worth mentioning.
it's a surprising error for sure, totally unrelated to the input code
just don't want to jump to a solution
https://github.com/clojure/core.specs.alpha/blob/master/src/main/clojure/clojure/core/specs/alpha.clj#L53
perhaps init-exprs shouldn't be any?
-- maybe they can be sensitive to the left hand side
“Non-sensical destructuring fails with surprising error message”
Probably my best Jira summary ever.
sounds good to me
Is associative destructuring sugar on top of get
? Or is that never specified anywhere? (The reason I ask is that, if so, it seems to be related to whether get
on a number is meant to return nil
.)
yes and yes, (get 3 :foo) returns nil
this is a number at macroexpansion time, and I don't think that can ever make sense
(let [{:keys [a]} 42] ...)
if (get 3 :foo)
makes sense then I guess (let [{:keys [foo]} 3]..)
makes sense as well?
Cool, just trying to keep it straight in my head (outside of this nonsensical example). Associative destructuring is sugar for get
and sequential is sugar for nth
with nil
for unknown, in my simplified mental model.
(get 3 :foo)
does not make sense, even though it (correctly) doesn't blow up
but most of the time you don't know it's a constant
Right, and we will never really know whether (get 3 :foo)
is meant to return nil
or not, I bet.
we do -- it is supposed to return nil
Says who?
says rich
there's an explicit bottom-out in RT.getFrom()
get on something that doesn't know how to get returns nil
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L786
Yeah, that's what I resorted to as the spec of the behavior as well. Using the impl as the spec.
this one is by design
are you implying there are things in Clojure which are not by design?
If we accept that, then Eric's last point makes sense.
(get 3 :foo) returns the correct thing (nil) but makes no sense from a user perspective
UB
destructuring is slightly different in that you have the opportunity to check a spec at macroexpansion time
Speculative decided to allow any for the map
argument to get
https://github.com/borkdude/speculative/blob/master/src/speculative/core.cljc#L203
I agree with @ghadi, since
user=> (def lol 3)
#'user/lol
user=> (let [{:keys [foo]} lol] (println foo))
works as expectedthat works because it is not known that it is a compile time constant
there's no inference across var deref
((fn [x] (get x :foo)) 3)
would be another example
mmm, I guess my point was that if the above example would fail with an error, I’d be a little unhappy, because it’s not always clear that a var contains a constant
where as (let [{:keys [foo]} 3] ...)
is just plain, well, non-sensical.
BTW, since you linked to https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L786
(and I’m not suggesting a rewrite/refactor, just being curious)
are there reasons (like performance), for duplicating the code in get(Object coll, Object key, Object notFound)
and get(Object coll, Object key)
?
I’d imagine that the latter could be implemented in terms of the former?
(too long since I programmed Java to remember if there are problems related to overloading static fns)
performance for that one
get(m k not-found) on a java.util.Map has to check contains first, then call get
though on an ILookup is simply m.valAt(k, notfound)
same reason get() and getFrom() is split, too: performance. Put the fastpath into a small, fast method
nth / nthFrom, same deal
So that it can be inlined?
yeah
Cool, thanks 🙂