views:

74

answers:

2

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.

+2  A: 

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.

Michał Marczyk
+3  A: 

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).

Alex Taggart