Share your thoughts in the 2024 State of Clojure Survey!

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

+1 vote
in Compiler by

When you type hint a var such as:

(def ^{:tag 'long} k 100)

And refer to it in a let binding:

(let [i k]
  (+ i 10))

The binding is unable to properly infer that it is a primitive type as it was hinted:

(set! *unchecked-math* :warn-on-boxed)
(def ^{:tag 'long} k 100)
(let [i k]
  (+ i 10))
;;=> Boxed math warning, unchecked_add(java.lang.Object,long).

It looks like it might be caused by a bug in the Compiler where for some reason, the expression of the var k is marked as not having a java class, even though it has one:

(defmacro inspect-local []
  (println
   (into {}
         (map (fn[[k v]]
                [k {:tag (.-tag v)
                    :class (.getJavaClass v)
                    :primitive? (.isPrimitive (.getJavaClass v))
                    :has-java-class? (.hasJavaClass v)}]))
         &env)))

(let [i k]
  (inspect-local)
  (+ i 10))

;;=> {i {:tag nil, :class long, :primitive? true, :has-java-class? false}}

As we can see, .hasJavaClass returns false, but .getJavaClass returns the Long/TYPE class.

I suspect that the compiler checks for .hasJavaClass before calling .getJavaClass, and since .hasJavaClass is erroneously false here, it would cause the compiler to think it isn't, and that the type can't be inferred.

I'm not too sure where in the Compiler this is, but I found the following: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L1341

Where we see the compiler is first checking for .hasJavaClass:

 if(e instanceof MaybePrimitiveExpr && e.hasJavaClass() && ((MaybePrimitiveExpr)e).canEmitPrimitive())
by
AFAIK primitive type hints only have effect in local bindings. Inferring them from var type hints isn't supported. I'm not sure where this is documented so it would still be good to dig that up.
by
Found it, posted it as answer

2 Answers

+1 vote
by

The documentation says only "local contexts" are supported when it comes to primitive type hints:

https://clojure.org/reference/java_interop#primitives

The tags on var (return types) aren't supported for primitive type hints.

+1 vote
by

Regardless of the type hint here, vars are always Objects and it will be a boxed value in the example usage (so not a primitive). However, if marked as a ^:const AND a primitive, the compiler will inline the var in the let with a primitive long and in that case it will be a primitive.

(set! *unchecked-math* :warn-on-boxed)

(def ^{:tag 'long :const true} k 100)

(let [i k] (+ i 10)))

will not warn. :const is often used incorrectly, but this is a good usage for it.

...