While trying out the new proxy stuff, this happened on compilation, though I’m not sure if this is related to shadow-cljs or not.
v3.3.1
3.3.2 works 👍
I noticed that when I use a var coming from :refer-global in a macro, I need to use that same :refer-global in each namespace where I use the macro in order to silence warnings like:
Use of undeclared Var /Array
It does seem like a false positive: my tests passed without doing this.“In a macro” is ambiguous. Do you mean in the macro definition or call?
Maybe an example would help
Sure, sorry:
(defmacro acc-append!
"Append a single value to the accumulator. Mutates in place, returns the accumulator."
[acc v]
`(macros/case
:clj (doto ~acc (ArrayList/.add ~v))
:cljs (doto ~acc (Array/.push ~v))))
Where macros is Macrovich, and Array is
(:refer-global :only [Array])So write that in NS1, then use acc-append! from NS2, and it will complain about Array being undeclared.
why is that a macro in the first place? but yeah, don't think this is going to work with "normal" CLJS either?
(defn acc-append!
"Append a single value to the accumulator. Mutates in place, returns the accumulator."
[acc v]
#?(:clj (ArrayList/.add acc v)
:cljs (.push acc v))).push doesn't return the array
then add the doto back ... just wanted to show defn example
@henrik I wonder what is in your ns form. can you make the example fully self-contained? AFAIK you can't use :refer-global in a .clj namespace so I don't know how this would work in "normal" CLJS either?
It’s a macro because inlining made a huge performance difference for the particular use case. Either way, is “don’t use macros” the generalizeable answer to this?
I was just asking another question :)
you can totally make this work in a macro without :refer-global
well the essence is that of (Array/.push ~v) the Array/ bit is completely pointless. it does absolutely nothing to the generated code and is in fact identical to just (.push ~v), so why bother. For CLJS only of course.
wait, is it actually identical? otherwise it is actually worse to write the Array/ bit 😛, since it will actually Reflect, the thing you are trying to prevent in CLJ 😛
:)
well yeah, it is actually way way worse.
hmmm wonder if my implementation is actually just bad or if its the CLJS side
console.log((function (){var G__96388 = [];
var G__96389 = (1);
return ((function (x, ...args) { return Reflect.apply(Array.prototype.push, x, args) }).cljs$core$IFn$_invoke$arity$2 ? (function (x, ...args) { return Reflect.apply(Array.prototype.push, x, args) }).cljs$core$IFn$_invoke$arity$2(G__96388,G__96389) : (function (x, ...args) { return Reflect.apply(Array.prototype.push, x, args) }).call(null,G__96388,G__96389));
})());
this is horrible 😛vs.
console.log([].push((1)));I agree with Thomas that you could use a function or simplify the macro to just use .push . And very good point that Array/.push will use Reflect and makes things slower. I don't actually think that turning this into a function will have a huge impact on performance.
Besides that, I wonder I wonder if the macro lives in a separate .clj namespace and where the :refer-global lives. It would seem horrible to have to add a :refer-global everywhere you use the macro but I think there's not really another option and that's painful too. Another reason to avoid it.
holy shit @thheller that expansion looks totally wrong since side effects happen multiple times
nah, just the usual "trying to figure out if IFn impl" noise
doesn't this actually execute the code while it's trying to figure out IFn?
return Reflect.apply(Array.prototype.push, x, args) }).cljs$core$IFn$_invoke$arity$2oh the thing is wrapped inside parens
but the function is constructed multiple times which is way too noisy
yeah looks really bad and probably shouldn't be this way. can't check if its just bad in shadow-cljs or CLJS as well. could be that I just missed something from the impl.
I don't see this with vanilla CLJS and :static-fns true :
cljs.user=> (str (fn [] (doto #js [] (Array/.push 1))))
"function (){\nvar G__15503 = [];\n(function (x, ...args) { return Reflect.apply(Array.prototype.push, x, args) }).call(null,G__15503,(1));\n\nreturn G__15503;\n}"
Don't know which test code you used exactly(js/console.log (Array/.push #js [] 1))
Wait, Array/.push reflects and .push doesn’t? The exact opposite of Clojure?
I can absolutely just omit it. The benefits would be:
• Avoid unnecessary reflection
• Self-documenting
But if the first doesn’t hold true, I don’t know if it’s worth it. Moreover, it brings into question: why have them at all in CLJS?
ok vanilla CLJS:
cljs.user=> (str (fn [] (js/console.log (Array/.push #js [] 1))))
"function (){\nreturn console.log((function (){var G__15508 = [];\nvar G__15509 = (1);\nreturn (function (x, ...args) { return Reflect.apply(Array.prototype.push, x, args) }).call(null,G__15508,G__15509);\n})());\n}"> Moreover, it brings into question: why have them at all in CLJS? We questioned this too, but David wanted to add it because of Clojure compat
ok, dunno why its missing the IFn bit, but still bad
We questioned this too, but David wanted to add it because of Clojure compatYeah, the value proposition seems dubious then. Especially as the Clojure compat then seems sort of cosmetic, rather than actual ~1:1 behaviour
it's not entirely cosmetic, since Array/.push standalone becomes a function that you can just pass around. But writing #(.push ...) isn't that bad. I agree that it has marginal benefits
No, that’s true. Treating them as values and the “self-documenting” part is still there. But arguably, there would be less indirection if it just compiled to #(.push ...) then? And both prior benefits would still remain true.
Either way, thank you for explaining this to me. The solution in my particular case then is just to not use method values.
Though it remains that they can’t be emitted from macros without yielding warnings.
This is a more complete context of how I set it up.
The NS that requires ….accumulator and uses acc-append! will yield warnings until #?(:cljs (:refer-global :only [Array])) is added to that NS as well.
yeah, that's what I would expect
Ah, OK. I don’t really understand CLJS under the hood, so for me it was surprising. The Clojure equivalent doesn’t behave like this.
CLJS macros live in a different world than the normal runtime program
.cljc just makes this even more confusing
Right. My tests for the functionality of this ns passed, suggesting that it somehow worked, but still yielded the warning.
I bet you got a warning about Array not being resolved. It still works since then it just falls back to js/Array probably. But it isn't resolved because you didn't have a :refer-global in your macro-consuming namespace, so it doesn't mean anything there
Something like that would explain it. OK, ticket closed due to user bright-eyed naiveté.
more info here too: https://clojurians.slack.com/archives/C07UQ678E/p1764588493068639
FWIW I have a "fix", still need to do some testing to see if this actually does fix it properly. https://github.com/thheller/shadow-cljs/commit/da87191e2db83c3cb34415cdef19d479e22a1795
ah yes, in invoke position this makes sense
yeah I forgot about that. will add support for :refer-global later
And :require-global, right
to be honest I have no clue what was added. didn't pay much attention.
I still prefer just js/Proxy 😉
CLJS-3233: :refer-global + :only, :require-global• and `Clojure method values syntax support Not sure if you use the exact same cljs.analyzer as vanilla CLJS does
that should just work, although I didn't actually test
me neither. all changes are included in the changelog :) https://github.com/clojure/clojurescript/blob/master/changes.md
maybe the destructuring + PAM is also relevant to shadow, don't know if you have custom destructuring logic or not
it does not
Method values might or might not work, but it seems like :refer-global is a precondition, so can’t verify. Example from release notes:
(refer-global :only '[String])
(map String/.toUpperCase ["foo" "bar" "baz"]) ;; => ("FOO" "BAR" "BAZ")you can test Object/.toString
oh wait, that also requires js/Object, nevermind
Yeah, they’re all under js/ normally, right?
except Math
but I guess that's a weird special case
well you can do it with non-js things too
PersistentArrayMap/.toString seems to work fine
confirmed:
cljs.user=> (map PersistentArrayMap/.toString [{}])
("{}")Static method:
(println (mapv Math/floor [1.111 2.111 3.111 4.999 5.999]))
;; => [1 2 3 4 5]that always worked and has not changed
cljs.user=> (def String js/String)
#'cljs.user/String
cljs.user=> (map String/.toUpperCase ["a"])
("A")Ah, then I don’t know. I can’t see that Math has any non-static methods.