<< Back to previous view

[CLJ-906] defrecord with protocol implementation fails when field and method names collide Created: 01/Jan/12  Updated: 01/Mar/13  Resolved: 27/Nov/12

Status: Closed
Project: Clojure
Component/s: None
Affects Version/s: Release 1.2, Release 1.3
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Brian Stiles Assignee: Unassigned
Resolution: Declined Votes: 0
Labels: None


The following produces a NullPointerException:

(defprotocol Foo (act [this]))
(defrecord Fred []
(act [this] (println "done.")))
(defrecord Bar [act] ;; <<<<<======= Field name is the same as method name.
(act [this] (act (Fred.)))) ;; <<<<<====== Behaves as (nil (Fred.))
(act (Bar. nil))

Exception in thread "main" java.lang.NullPointerException
at protocol.Bar.act(protocol.clj:8)
at protocol$eval66.invoke(protocol.clj:9)
at clojure.lang.Compiler.eval(Compiler.java:6465)
at clojure.lang.Compiler.load(Compiler.java:6902)
at clojure.lang.Compiler.loadFile(Compiler.java:6863)
at clojure.main$load_script.invoke(main.clj:282)
at clojure.main$script_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:426)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.lang.Var.invoke(Var.java:401)
at clojure.lang.AFn.applyToHelper(AFn.java:161)
at clojure.lang.Var.applyTo(Var.java:518)
at clojure.main.main(main.java:37)

The following works as expected:

(defprotocol Foo (act [this]))
(defrecord Fred []
(act [this] (println "done.")))
(defrecord Bar [x] ; <<== Field name is the DIFFERENT than method name
(act [this] (act (Fred.))))
(act (Bar. [1 2 3]))


The following also works:

(defprotocol Foo (act [this]))
(defrecord Fred [])
(defrecord Bar [act]) ; <<== Field name is the same as method name
(extend-protocol Foo
(act [this] (println "done."))
(act [this] (act (Fred.))))
(act (Bar. [1 2 3]))

Comment by Timothy Baldridge [ 27/Nov/12 2:03 PM ]

The thing to remember here is that protocol functions are not (only) methods on a class, they are functions global to the namespace. So notice the subtle difference here:

user=> (defprotocol Foo (act [this]))
user=> (defrecord Fred [act] Foo (act [this] (act this)))
user=> (Fred. 42)
#user.Fred{:act 42}
user=> (act (Fred. 42))
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn user.Fred (NO_SOURCE_FILE:1)

user=> (defrecord Fred [act] Foo (act [this] (user/act this)))
user=> (act (Fred. 42))
StackOverflowError user.Fred (NO_SOURCE_FILE:1)

So, it you want to access the data, you can use "act" directly, if you want to recursively call act (the protocol function), you can use recur, or the fully qualified name. Also, since these are records we're talking about, the following also works:

user=> (defrecord Fred [act] Foo (act [this] (:act this)))
user=> (act (Fred. 42))

Comment by Timothy Baldridge [ 27/Nov/12 2:07 PM ]

Declined since this is not really a bug. There is a work-around and no obvious solution to the more general problem of defrecord name collisions. If this still bugs you, please feel free to bring it up on clojure-dev, and we'll open a new ticket once a discussion has been had.

Generated at Thu Nov 26 17:26:26 CST 2015 using JIRA 4.4#649-r158309.