clojurescript

Ben Sless 2025-06-29T07:25:31.160119Z

Hey all, I need a hand getting oriented. I have zero experience with cljs, and contributed a PR to a cljc project. Everything is ok in the clj side, but in cljs tests fail in a rather opaque manner:

actual: #object[RangeError RangeError: Maximum call stack size exceeded]
There's no stack trace or hint to the root cause, and to my embarrassment, I have no clue how to even start a cljs repl or recreate it in a manner I can debug. Can you please advise? (link to PR in thread)

Ben Sless 2025-06-29T07:25:50.514549Z

https://github.com/brandonbloom/fipp/pull/89

thheller 2025-06-29T07:31:20.455079Z

two things. you can't rely on identical? when it comes to keywords in CLJS, hence there is a keyword-identical? you are supposed to used, or just = (which checks identical? as its first step anyway)

👀 1
thheller 2025-06-29T07:31:46.028439Z

second you are using (type x), which unlike CLJ is not really a "class"

✍️ 1
thheller 2025-06-29T07:35:08.182909Z

a REPL you could get via clj -Sdeps '{:paths["src"],:deps{thheller/shadow-cljs{:mvn/version"3.1.7"}}}' -M -m shadow.cljs.devtools.cli browser-repl in that repo

thheller 2025-06-29T07:36:08.676089Z

(also opens nrepl server, which you can connect your editor to via .shadow-cljs/nrepl.port)

Ben Sless 2025-06-29T07:36:59.061549Z

I just figured out how to launch the embedded node REPL from within a lein repl

Ben Sless 2025-06-29T07:37:17.672999Z

At least I get a stack trace now

👍 1
Ben Sless 2025-06-29T07:38:22.045739Z

I suspect it's not a full stack trace though

Ben Sless 2025-06-29T07:38:40.321559Z

What should I use instead of type?

thheller 2025-06-29T07:39:36.511199Z

I don't know it thats actually related to the problem. JS just doesn't have a real class system and type just gives you the constructor fn

Ben Sless 2025-06-29T07:41:19.880819Z

I'm assuming the error I'm getting is justified in that I just exceeded the maximum call stack size

thheller 2025-06-29T07:42:36.397549Z

the test data doesn't look like anything that should reach even close to that limit

thheller 2025-06-29T07:43:53.719259Z

maybe it just gets stuck in an infite recur loop?

Ben Sless 2025-06-29T07:44:12.119309Z

No, I increased the stack size and the test passed

2025-06-29T09:06:55.507329Z

The lack of keyword-identical? in Clojure is a hzaard (or in other words, the lack of clj/cljs parity is a hazard and I guess it could be resolved more easily by the addition of that abomination to Clojure than by its re-removal from ClojureScript, which originally did not have it). I did not see an "ask" already, so I submitted one: https://ask.clojure.org/index.php/14593/could-keyword-identical-be-added-to-clojure Feel free to vote on it if you agree.

thheller 2025-06-29T09:09:28.597889Z

its tricky. better just not use a keyword here to begin with. I'd rather remove the reason why keyword-identical? exists in the first place. but thats another very deep rabbit hole 😛

Ludger Solbach 2025-06-29T12:00:12.945629Z

Or have identical? handle the special case of keywords in cljs. Then we would have a unified interface and correct functionality in both.

Ludger Solbach 2025-06-29T12:01:42.158309Z

It my call a private keyword-identical?function. Or am I missing something fundamental here?

thheller 2025-06-29T12:04:24.409649Z

changing identical? makes it slower for the "normal" case, so not a worthy trade off

thheller 2025-06-29T12:05:58.445149Z

problem is that keywords in CLJS are not interned like they are in CLJ, which has to do with :advanced dead-code removal

thheller 2025-06-29T12:06:27.223579Z

so in development each time a keyword is used its a literal new cljs.core.Keyword in the JS code, hence never identical?

thheller 2025-06-29T12:06:46.614989Z

in optimized builds thats actually optimized out, so it is identical? there (for this case, not always)

thheller 2025-06-29T12:09:58.459729Z

so its better to just use a definite unique value like (js/Object.) or (Object.) in CLJ, and not relying on the fact that keywords are interned or not

thheller 2025-06-29T12:13:57.194749Z

(defn- cached-override?
  [cache x]
  (let [clazz (type x)
        not-found (#?(:clj (Object.) :cljs (js-obj)))
        ret (get @cache clazz not-found)]
    (if (identical? not-found ret)
      (let [ret (satisfies? IOverride x)]
        (vswap! cache assoc clazz ret)
        ret)
      ret)))

thheller 2025-06-29T12:13:59.687499Z

something like that. could move that obj into a top-level def to avoid the allocation, but you get the idea

Ben Sless 2025-06-29T12:33:51.384159Z

Will that top level def "behave", for lack of a better word, in cljs?

Ben Sless 2025-06-29T12:34:34.558379Z

They'll have to be top level for correctness 🤔

thheller 2025-06-29T12:35:01.350039Z

(def not-found (#?(:clj (Object.) :cljs (js-obj))))? of course

💯 1
Ben Sless 2025-06-29T12:35:32.856449Z

I think there's an extra paren pair there, but sure

thheller 2025-06-29T12:36:02.746639Z

right yeah

Ben Sless 2025-06-29T12:36:19.271789Z

(def not-found #?(:clj (Object.) :cljs (js-obj)))

(defn- cached-override?
  [cache x]
  (let [clazz (type x)
        ret (get @cache clazz not-found)]
    (if (identical? not-found ret)
      (let [ret (satisfies? IOverride x)]
        (vswap! cache assoc clazz ret)
        ret)
      ret)))

👍 1