Welcome! Please see the About page for a little more info on how this works.

0 votes
in ClojureScript by
{{js->clj}} says


  (cond

    [...]
                
    (coll? x)
    (into (empty x) (map thisfn x))


The gap is that {{(coll? x)}} checks for ICollection, while {{(empty x)}} requires IEmptyableCollection.  When x passes the first test but fails the second, {{js->clj}} throws "Error: No protocol method IEmptyableCollection.-empty defined for type : [object Object]."  

Inasmuch as {{js->clj}} is recursive through Javascript objects that tend to contain everything but the kitchen sink, it is inevitably a matter of best effort rather than high principle.  In a word, this is no place to split hairs.  If the collection is not emptyable, I expect {{js->clj}} to make some effort to preserve order but not type: use a vector!

5 Answers

0 votes
by

Comment made by: rohitaggarwal

(link: ~pbwolf) can you give a concrete failing case?

0 votes
by

Comment made by: jszakmeister

It's hard to give a concrete case here--it seems to only really come out with advanced compilation. I had a routine that was returning something like this from a server: {"Br": 0, "B": 100, "P": 1000} as JSON. js->clj would trip generate the "no protocol method" error mentioned in the description on this. I found that if I moved this snippet:

`
(identical? (type x) js/Object)
(into {} (for [k (js-keys x)]

       [(keyfn k) (thisfn (unchecked-get x k))]))

`

ahead of this snippet:

(coll? x) (into (empty x) (map thisfn x))

that the error went away--I'm not sure if it's the right thing to do though. I suspect that the keys are somehow colliding again with known things, and something is being treated like a collection when it is not. Then, when (empty x) is called, we fail because we the object doesn't implement the required protocol.

I thought this kind of situation was fixed in the past by CLJS-1658 and 7e15b1f2b894d93ef94ff86d75226f1fd3919580, but maybe something else is going on that we're hitting false positives again.

0 votes
by

Comment made by: thheller

This is probably not fixable without breaking the semantics of js->clj for someone else. At least I can't think of something easy.

Here is a short reproduction of whats happening:

  • coll? checks ICollection which is a fast-path protocol
  • fast-path protocols do a bit check, instead of the previously fixed sentinel check
  • bit check is done on a property named {{cljs$lang$protocol_mask$partition0$}} which gets shortened by Closure to something unknown. Given the "short" keys in your example Object that is very likely to conflict given that Closure starts with a,b,c,...

This can be reproduced in the Node REPL:

`
[6:1]~cljs.user=> (def obj #js {"cljs$lang$protocol_mask$partition0$" 8})

'cljs.user/obj

[6:1]~cljs.user=> (coll? obj)
true
[6:1]~cljs.user=> (empty obj)
eval error Error: No protocol method IEmptyableCollection.-empty defined for type object: [object Object]
`

One easy fix is to just forget about js->clj and use something that only supports JSON values and doesn't use protocols.

I wrote one you can use here:
https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/json.cljs#L4-L37

Maybe we should add a json->clj to core and properly document the problematic behaviour of js->clj?

0 votes
by

Comment made by: jszakmeister

{quote}
Maybe we should add a json->clj to core and properly document the problematic behaviour of js->clj?
{quote}

I think that would be advisable. I implemented my own json->clj in my project that worked around this issue a while back, but I think something in the core would be very beneficial here.

BTW, nice reproduction recipe. It's unfortunate that advanced compilation shortens that to something that potentially conflicts.

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJS-2062 (reported by alex+import)
...