shadow-cljs

henrik 2025-11-27T10:49:11.710409Z

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

henrik 2025-12-01T09:15:52.328909Z

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.

borkdude 2025-12-01T09:21:41.303409Z

“In a macro” is ambiguous. Do you mean in the macro definition or call?

borkdude 2025-12-01T09:21:52.833359Z

Maybe an example would help

henrik 2025-12-01T09:23:48.209209Z

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])

henrik 2025-12-01T09:26:24.743959Z

So write that in NS1, then use acc-append! from NS2, and it will complain about Array being undeclared.

thheller 2025-12-01T10:55:00.192559Z

why is that a macro in the first place? but yeah, don't think this is going to work with "normal" CLJS either?

thheller 2025-12-01T10:56:13.355549Z

(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)))

borkdude 2025-12-01T10:56:43.349559Z

.push doesn't return the array

thheller 2025-12-01T10:57:44.887439Z

then add the doto back ... just wanted to show defn example

👍 1
borkdude 2025-12-01T10:58:01.647739Z

@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?

henrik 2025-12-01T10:59:27.647509Z

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?

borkdude 2025-12-01T11:00:40.252159Z

I was just asking another question :)

borkdude 2025-12-01T11:00:58.211159Z

you can totally make this work in a macro without :refer-global

thheller 2025-12-01T11:02:48.407189Z

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.

thheller 2025-12-01T11:04:22.201369Z

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 😛

borkdude 2025-12-01T11:04:30.844399Z

:)

thheller 2025-12-01T11:06:02.171699Z

well yeah, it is actually way way worse.

thheller 2025-12-01T11:06:34.770859Z

hmmm wonder if my implementation is actually just bad or if its the CLJS side

thheller 2025-12-01T11:06:58.952839Z

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 😛

thheller 2025-12-01T11:07:31.707229Z

vs.

console.log([].push((1)));

borkdude 2025-12-01T11:08:58.357009Z

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.

borkdude 2025-12-01T11:10:21.486469Z

holy shit @thheller that expansion looks totally wrong since side effects happen multiple times

thheller 2025-12-01T11:11:03.220269Z

nah, just the usual "trying to figure out if IFn impl" noise

borkdude 2025-12-01T11:11:34.109779Z

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$2

borkdude 2025-12-01T11:11:58.157699Z

oh the thing is wrapped inside parens

borkdude 2025-12-01T11:12:14.976409Z

but the function is constructed multiple times which is way too noisy

thheller 2025-12-01T11:13:26.806679Z

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.

borkdude 2025-12-01T11:17:54.263399Z

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

thheller 2025-12-01T11:23:43.643899Z

(js/console.log (Array/.push #js [] 1))

henrik 2025-12-01T11:24:08.984549Z

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?

borkdude 2025-12-01T11:25:00.078109Z

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}"

borkdude 2025-12-01T11:25:41.434469Z

> 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

thheller 2025-12-01T11:25:51.819569Z

ok, dunno why its missing the IFn bit, but still bad

henrik 2025-12-01T11:30:06.216889Z

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

borkdude 2025-12-01T11:32:41.034079Z

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

henrik 2025-12-01T11:50:02.663929Z

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.

henrik 2025-12-01T11:51:26.510789Z

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.

borkdude 2025-12-01T11:54:53.853189Z

yeah, that's what I would expect

henrik 2025-12-01T12:01:26.173999Z

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.

borkdude 2025-12-01T12:03:03.923319Z

CLJS macros live in a different world than the normal runtime program

borkdude 2025-12-01T12:03:46.760959Z

.cljc just makes this even more confusing

henrik 2025-12-01T12:06:00.734449Z

Right. My tests for the functionality of this ns passed, suggesting that it somehow worked, but still yielded the warning.

borkdude 2025-12-01T12:08:07.033189Z

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

henrik 2025-12-01T12:38:33.500499Z

Something like that would explain it. OK, ticket closed due to user bright-eyed naiveté.

borkdude 2025-12-01T12:39:24.384609Z

more info here too: https://clojurians.slack.com/archives/C07UQ678E/p1764588493068639

👍 1
thheller 2025-12-01T13:09:12.303079Z

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

borkdude 2025-12-01T13:19:34.281269Z

ah yes, in invoke position this makes sense

thheller 2025-11-27T10:50:05.601119Z

yeah I forgot about that. will add support for :refer-global later

➕ 1
🙏 2
borkdude 2025-11-27T10:54:16.246069Z

And :require-global, right

thheller 2025-11-27T10:54:45.965789Z

to be honest I have no clue what was added. didn't pay much attention.

thheller 2025-11-27T10:55:11.827079Z

I still prefer just js/Proxy 😉

borkdude 2025-11-27T10:55:25.297739Z

CLJS-3233: :refer-global + :only, :require-global

borkdude 2025-11-27T10:56:00.229169Z

• and `Clojure method values syntax support Not sure if you use the exact same cljs.analyzer as vanilla CLJS does

thheller 2025-11-27T10:56:18.619749Z

that should just work, although I didn't actually test

borkdude 2025-11-27T10:56:54.641219Z

me neither. all changes are included in the changelog :) https://github.com/clojure/clojurescript/blob/master/changes.md

borkdude 2025-11-27T10:57:28.715399Z

maybe the destructuring + PAM is also relevant to shadow, don't know if you have custom destructuring logic or not

thheller 2025-11-27T10:58:08.508969Z

it does not

henrik 2025-11-27T11:00:00.807679Z

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")

borkdude 2025-11-27T11:00:54.032019Z

you can test Object/.toString

borkdude 2025-11-27T11:01:25.693349Z

oh wait, that also requires js/Object, nevermind

henrik 2025-11-27T11:01:41.664149Z

Yeah, they’re all under js/ normally, right?

borkdude 2025-11-27T11:01:49.091079Z

except Math

borkdude 2025-11-27T11:03:24.056769Z

but I guess that's a weird special case

thheller 2025-11-27T11:03:35.420909Z

well you can do it with non-js things too

thheller 2025-11-27T11:04:49.139649Z

PersistentArrayMap/.toString seems to work fine

borkdude 2025-11-27T11:06:50.776849Z

confirmed:

cljs.user=> (map PersistentArrayMap/.toString [{}])
("{}")

henrik 2025-11-27T11:08:10.512529Z

Static method:

(println (mapv Math/floor [1.111 2.111 3.111 4.999 5.999]))

;; => [1 2 3 4 5]

thheller 2025-11-27T11:08:40.634059Z

that always worked and has not changed

borkdude 2025-11-27T11:08:58.308119Z

cljs.user=> (def String js/String)
#'cljs.user/String
cljs.user=> (map String/.toUpperCase ["a"])
("A")

👍 1
henrik 2025-11-27T11:08:59.918249Z

Ah, then I don’t know. I can’t see that Math has any non-static methods.