Wrong FontRenderContext constructor called


just got this problem with instantiating a FontRenderContext in Clojure 1.10.1:

(java.awt.font.FontRenderContext. nil true true)


Execution error (IllegalArgumentException) at java.awt.font.FontRenderContext/<init> (FontRenderContext.java:156).
AA hint:true

There are two public constructors in FontRenderContext:

FontRenderContext(AffineTransform tx, boolean isAntiAliased, boolean usesFractionalMetrics)
FontRenderContext(AffineTransform tx, Object aaHint, Object fmHint)

Reason is that the second one (Object args) is called albeit booleans are explicitly used as args.

I could not find a way to call the first one, so I used

(FontRenderContext. nil RenderingHints/VALUE_TEXT_ANTIALIAS_ON, RenderingHints/VALUE_FRACTIONALMETRICS_ON)

as a workaround, but booleans would be nicer :slight_smile: .

Did you try with type hints?

yes, I tried something like

(java.awt.font.FontRenderContext. ^java.awt.geom.AffineTransform nil, ^boolean true, ^boolean true)

but got

Syntax error reading source at (REPL:1:69).
Metadata can only be applied to IMetas

The following seems to work. I think it’s because the constructor needs boolean primitives vs instances of java.lang.Boolean . I believe true and false are the same as java.lang.Boolean/TRUE and java.lang.Boolean/FALSE. Someone please correct me if I’m wrong.

user=>  (java.awt.font.FontRenderContext. nil (boolean true) (boolean true)) 

#object[java.awt.font.FontRenderContext 0x38f116f6 "java.awt.font.FontRenderContext@52d21dca"]
user=> (let[^boolean t true] 
         (java.awt.font.FontRenderContext. nil t t))

#object[java.awt.font.FontRenderContext 0x74a6a609 "java.awt.font.FontRenderContext@52d21dca"]

cool, thanks a lot!

I just checked
(identical? (type true) (type (boolean true)))
and it yields true, but well :slight_smile:

Haha yeah this is confusing. I think what’s happening is that the primitive is getting boxed to Object since type will end up calling class which expects an Object.

1 Like

That’s because:

Type hints are metadata tags placed on symbols or expressions that are consumed by the compiler. They can be placed on function parameters, let-bound names, var names (when defined), and expressions

You can not type hint values such as nil and true. That’s why you need to wrap the value in something that can be hinted so the compiler can use the hint.

That’s correct. The boolean function returns a primitive boolean, not a Boolean. The literals true and false return a Boolean.

Alright, I’ll explain, but it gets hairy, even I get confused explaining it :stuck_out_tongue:.

Functions in Clojure are always created with type Object for their parameters. Type hinting does not change that (except for long, double and arrays). Thus the type function takes Object as an argument, and no overload exist which takes a primitive.

When Clojure invokes the type function, it will see that only an Object overload exists, and it will thus perform auto-boxing of the primitive before calling it.

Even if there was an overload for type which took a primitive, the implementation of it calls .getClass on the argument. Clojure also performs auto-boxing when method instances are used on primitives. In such scenario, the imaginary type which accepts a primitive would be called, but prior to calling .getClass on the primitive argument, Clojure would also perform auto-boxing.

Now, something even more confusing is that primitives do not have types at runtime. The memory only contains the value, no additional meta-data. So you can’t introspect a primitive at runtime, for example, to ask it what type it is. The only way is to do it at compile time, and in Clojure I only know that recur will type check for primitives at compile time:

(loop [a (boolean true) i 0]
  (when (zero? i)
    (let [a true]
      (recur a (inc 0)))))
;=> a is not matching primitive, had: java.lang.Boolean, needed: boolean

;; Because true is Boolean, but if we corce to boolean it works:
(loop [a (boolean true) i 0]
  (when (zero? i)
    (let [a (boolean true)]
      (recur a (inc 0)))))
;=> nil

Now in your case, you have a method which can take Object, Object or boolean, boolean. Type hinting or passing a primitive to any of them will allow Clojure to figure out which one to call. In the case of a conflict, like you pass a boolean, Boolean, normally Java would fail to compile that since it is ambiguous, but Clojure prioritizes the primitive, and will unbox the Boolean automatically for us. So you only need to coerce or type hint one of them.

Another thing is, type hinting is not the same as coercing. A type hint of ^boolean as so:

(let [^boolean a true
       ^boolean b true]
   (java.awt.font.FontRenderContext. nil a b))

Results in:

final Object a = Boolean.TRUE;
final Object b = Boolean.TRUE;
return new FontRenderContext(null, (boolean)a, (boolean)b);

So the binding a and b are of type Object, but will be casted to boolean if there is an overload of that type. While:

(let [a (boolean true)
       b (boolean true)]
   (java.awt.font.FontRenderContext. nil a b))

Creates a binding of type boolean as such:

final boolean a = RT.booleanCast(Boolean.TRUE);
final boolean b = RT.booleanCast(Boolean.TRUE);
return new FontRenderContext(null, a, b);

This is only true in certain local context though. Like I said before, Clojure does not allow you to create functions with parameters of type boolean, but let and loop will allow it.

Alright, I’m done for today :stuck_out_tongue:

If you want to learn more about this stuff, I recommend reading this: https://clojure.org/reference/java_interop especially the later stuff on coercions and optimizations. And make heavy use of clj-java-decompiler.core/decompile from https://github.com/clojure-goes-fast/clj-java-decompiler in your REPL session. I have it loaded by default in all my REPLs, never know when I’ll need it.

1 Like

@didibus Awesome explanation. Learned a few things myself. Thank-you! :slight_smile:

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.