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

+1 vote
in ClojureScript by

Further to a recent conversation in the ClojureScript topic in Slack, Dan Sutton suggested that I create an issue here.

I think I may have found a bug in ClojureScript's type inference. I've created a minimal reproduction here:

https://github.com/paulbutcher/clojurescript-type-inference-bug

The following code generates a warning:

  cljs.core/-, all arguments must be numbers, got [#{js/clj-nil clj-nil} #{js/clj-nil clj-nil}] instead
  (- (ocall js/Math :random) (ocall js/Math :random))
  ^---

I would expect that I could fix it like this:

(- ^js/number (ocall js/Math :random) ^js/number (ocall js/Math :random))

But that gives me exactly the same error.

1 Answer

0 votes
by
selected by
 
Best answer

^js/number is not a valid hint for numbers. Anything starting with js/ is treated a hint for externs inference and therefore won't affect the warning in this case. It should just be ^number without the js ns.

Also note that ocall is a macro so whether or not it applies the metadata you supplied to the source form to the output form is up to that macro. Therefore it might not actually arrive in the compiler to where it needs to be.

Also consider using just (js/Math.random) instead of ocall. This is safe from renaming like all standard functions are. That'll also preserve the typehint properly.

by
Thanks Thomas, much appreciated.

Just for completeness, the code from which this was cut down actually calls functions in the Vega Javascript library: I switched to `js.Math.random` for the example to avoid the need to import Vega (the dangers of over simplifying!).

Is there any good documentation on this? One of the things I did try was `^number` and got the same warning, and I wasn't aware of the difference, or the fact that type hints won't work for macros?

Thanks again!
by
Type hints can work just fine for macros but remember that macros are functions that take one form and return another. If the macro does not explicitely "transfer" the metadata received from the input to the output it'll be lost. I'm guessing that `ocall` doesn't do this but I'm not sure.

Best to avoid ocall and such completely for interop IMHO.
by
Thanks Thomas.

If I wanted to get my head properly around this (e.g. to understand the difference between ^js/number and ^number) where should I look for documentation which will help me understand?
by
I'm not aware of any documentation regarding this. I'm sure there is some though.

Simply said there are just a couple typehints that the compiler recognizes. There is "number" and "boolean" for exactly that. Then there is just "js" and "js/*" for externs inference basically, telling the compiler to collect externs to protect against renaming in :advanced compilation.

The difference really is just that it needs to be an exact match and "number" does not equal "js/number".
by
Great. Thanks for your help - much appreciated.
...