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

0 votes
in Compiler by

When working with loop, it sounds like the Clojure compiler tries to use primitive variables for better performance. From the "Support for Java Primitives" section.

let/loop-bound locals can be of primitive types, having the inferred, possibly primitive type of their init-form.
recur forms that rebind primitive locals do so without boxing, and do type-checking for same primitive type.

The below loop throws a syntax error.

(loop [x (pos? 1)]
  (when-not x
    (recur (first [false]))))
Syntax error (IllegalArgumentException) compiling fn* at (src/foo.clj:4:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean

The compiler appears to know that pos? returns a primitive boolean and (first [false]) does not (returns a boxed boolean Boolean?). Wrapping the recur arg in a call to boolean compiles.

(loop [x (pos? 1)]
  (when-not x
    (recur (boolean (first [false])))))
=> nil

What's interesting, however, is changing the recur arg to different functions.

(loop [x (pos? 1)]
  (when-not x
    (recur (pos? x))))
Syntax error (IllegalArgumentException) compiling fn* at (src/foo.clj:4:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean

Given the compiler knows pos? returns a primitive in the init form, it seems it should know it'd also return a primitive in the recur arg.

Like before, wrapping the pos? recur call in boolean allows the form to compile.

(loop [x (pos? 1)]
  (when-not x
    (recur (boolean (pos? x)))))
=> nil

If I change the arg to boolean to be a literal, I get the syntax error.

(loop [x (pos? 1)]
  (when-not x
    (recur (boolean 1))))
Syntax error (IllegalArgumentException) compiling fn* at (src/foo.clj:4:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean

That only occurs for numbers though. Passing a string, char, vector, or even a boolean all compile. e.g.,

(loop [x (pos? 1)]
  (when-not x
    (recur (boolean true))))
=> nil

Passing a literal boolean does not work.

(loop [x (pos? 1)]
  (when-not x
    (recur false)))
Syntax error (IllegalArgumentException) compiling fn* at (src/foo.clj:4:1).
 recur arg for primitive local: x is not matching primitive, had: java.lang.Boolean, needed: boolean

pos? is an inlined function. In the above cases it expands to (. clojure.lang.Numbers (isPos 1)). From the source of isPos, we find a static method that has the return type boolean.

static public boolean isPos(Object x){
	return ops(x).isPos((Number)x);
}

Since isPos is a static method, the compiler should always know it's type. From Type Hints:

Note that type hints are not needed for static members (or their return values!) as the compiler always has the type for statics.

Except the first example and the ones with literals, all the above functions call to static Java methods that return a primitive boolean. Why does the compiler not see the primitive type return value in the above cases?

1 Answer

0 votes
by
edited by

Not an answer, but some refinement of the problem:

Looks like true and false are boxed (at least in this case), as java.lang.Boolean, so this works:

user> (loop [x  (pos? 1)]
         (when-not x 
             (recur (.booleanValue true))))
nil

I think there's something going on with booleanCast that's not obvious. Since it's getting a literal Long value of 1, it's probably going down the object path, predicating the result on x != null. It seems like this is introducing a subtle difference for some reason in the primitive casting.

What if we double boolean it?

user> (loop [x  (pos? 1)]
    (when-not x 
        (recur  (boolean (boolean 1)))))
nil

We're oddly "good", since we're calling boolean on a primitive boolean, which is identity....

I'm betting folks more familiar with the compiler will have insight into this, and whether it's a bug or a feature. Looks like a bug to me.

What's more, via hinting, it's possible to get the compiler to complain about getting a boolean and needing a boolean. Odd

user> (loop [x  (pos? 1)]
    (when-not x 
        (recur  ^boolean (boolean 1))))
Syntax error (IllegalArgumentException) compiling fn* at (*cider-repl 
workspacenew\test:localhost:52469(clj)*:810:7).
 recur arg for primitive local: x is not matching primitive, 
had: boolean, needed: boolean
...