I created a type using defrecord
with type hints for the fields. However, I found that these type hints are not enforced in the constructors and I am able to do some strange things with them. Look at the snippet below for example:
user=> (defrecord Person [#^String name #^Integer age])
user.Person
user=> (seq (.getConstructors Person))
(#<Constructor public user.Person(java.lang.Object,java.lang.Object,
java.lang.Object,java.lang.Object)>
#<Constructor public user.Person(java.lang.Object,java.lang.Object)>)
user=> (Person. (Integer. 123) "abhinav")
#:user.Person{:name 123, :age "abhinav"}
The constructor signatures shown do not match with the type hints provided (they use Object
for both String
and Integer
) and I am able to construct objects with wrong field types.
Is there something wrong with my code or is it a bug in Clojure?
I am on Clojure 1.2.0-beta1.
Type-hints are used to avoid reflection; they are not (currently) used to statically type function or constructor args (the exception being primitives since they can't be subsumed under Object
). As such, they don't do much for a simple record, but they do matter when it comes to adding protocol implementation, e.g.:
user=> (set! *warn-on-reflection* true) true user=> (defprotocol P (foo [p])) P user=> (defrecord R [s] P (foo [_] (.getBytes s))) ; getBytes is a method on String Reflection warning, NO_SOURCE_PATH:6 - reference to field getBytes can't be resolved. user.R user=> (foo (R. 5)) java.lang.IllegalArgumentException: No matching field found: getBytes for class java.lang.Integer (NO_SOURCE_FILE:0) user=> (defrecord R [^String s] P (foo [_] (.getBytes s))) user.R user=> (foo (R. 5)) java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String (NO_SOURCE_FILE:0)
The difference between the two versions is that the latter emits bytecode calling String.getBytecode()
(hence the ClassCastException when passed an Integer), whereas the former needs to look up what exactly .getBytes
means with respect to the runtime object passed to the function (and that process fails when passed an Integer).
As far as I can tell, type hints on deftype
and defprotocol
fields are currently only enforced when a primitive type is involved:
(deftype Foo [^int x])
(Foo. 5) ; => OK
(Foo. :foo) ; => no go
;; ... and likewise with defprotocol
I have a very vague recollection of this being a recognised issue, though I'm not sure if the plan is to document this behaviour or to enforce non-primitive hints... I'll try to find out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With