Summary
Inside an inline protocol method impl on defrecord, a bare reference to one of the record's own fields fails to resolve. TODO.md:113 lists inline protocol impls as complete, and (:field this) / extend-type both work, so this looks like the field bindings simply are not installed as locals in the inline-method body scope.
Repro
(defprotocol P (f [this]))
(defrecord R [b] P (f [_] b))
(println (f (->R 7)))
$ cljrs run repro.cljc
Error: × Unable to resolve symbol: b
Expected 7 (matches JVM Clojure).
Matrix
Everything below on c02b399e (main), debug build.
| case |
result |
(defrecord R [b] P (f [_] b)) - bare field |
❌ Unable to resolve symbol: b |
(defrecord R [b] P (f [_ x] (+ b x))) - bare field + arg |
❌ same |
(defrecord R [b] P (f [this] b)) - this named rather than _ |
❌ same |
(defrecord R [a b] P (f [_] a)) - two fields |
❌ Unable to resolve symbol: a |
(defrecord R [b] Object (toString [_] (str b))) |
❌ same |
(defrecord R [b] P (f [this] (:b this))) - keyword access |
✅ 7 |
(defrecord R [b]) + (extend-type R P (f [this] (:b this))) |
✅ 7 |
(:b (->R 7)) - field access from outside |
✅ 7 |
So the record itself and its fields are fine; only the lexical scope of an inline method body is missing them.
Why it bites harder than it looks
The failure is not always a visible error. In a multi-file program the enclosing namespace's preamble is evaluated at startup, and a failure there appears to be swallowed - the program keeps running against a half-initialized namespace. In an AOT binary (cljrs compile) I got a silently wrong answer rather than a crash: the AOT binary printed nil, cljrs run on the same source printed 0, and the correct answer was 183850. The dispatch had quietly fallen through to a :default method.
That second part may deserve its own issue - happy to split it out if you'd prefer. Minimal shape:
;; helper.cljc
(ns helper)
(def ^:dynamic *tuning* 31)
(defprotocol IWeigh (weigh [this x]))
(defrecord Weigher [bias] IWeigh (weigh [_ x] (+ (* x *tuning*) bias)))
(defmulti route (fn [m] (:kind m)))
(defmethod route :alpha [m] (weigh (->Weigher 7) (:v m)))
(defmethod route :default [_] 0)
(defn contrib [x] (binding [*tuning* 37] (route {:kind :alpha :v x})))
(defn total [n] (reduce + (map contrib (range n))))
;; app.cljc
(ns app (:require [helper :as h]))
(defn -main [& _] (println (h/total 100)))
cljrs run app.cljc --src-path . → 0; cljrs compile app.cljc -o app --src-path . then ./app → nil; JVM Clojure → 183850.
Possibly related, possibly separate
deftype fails earlier - the positional constructor does not seem to be generated at all:
(defprotocol P (f [this]))
(deftype T [b] P (f [_] b))
(println (f (->T 7)))
;; Error: × Unable to resolve symbol: T
Environment
clojurust @ c02b399e (main)
cargo build -p cljrs, debug profile
- Linux x86_64, rustc 1.94 (needed
cargo update kstring --precise 2.0.2, since kstring 2.0.4 requires rustc 1.96)
Verified working alongside the above, for contrast: binding + dynamic vars, defmulti/defmethod, reduce/map/range, keyword field access, extend-type.
Summary
Inside an inline protocol method impl on
defrecord, a bare reference to one of the record's own fields fails to resolve.TODO.md:113lists inline protocol impls as complete, and(:field this)/extend-typeboth work, so this looks like the field bindings simply are not installed as locals in the inline-method body scope.Repro
Expected
7(matches JVM Clojure).Matrix
Everything below on
c02b399e(main), debug build.(defrecord R [b] P (f [_] b))- bare fieldUnable to resolve symbol: b(defrecord R [b] P (f [_ x] (+ b x)))- bare field + arg(defrecord R [b] P (f [this] b))-thisnamed rather than_(defrecord R [a b] P (f [_] a))- two fieldsUnable to resolve symbol: a(defrecord R [b] Object (toString [_] (str b)))(defrecord R [b] P (f [this] (:b this)))- keyword access7(defrecord R [b])+(extend-type R P (f [this] (:b this)))7(:b (->R 7))- field access from outside7So the record itself and its fields are fine; only the lexical scope of an inline method body is missing them.
Why it bites harder than it looks
The failure is not always a visible error. In a multi-file program the enclosing namespace's preamble is evaluated at startup, and a failure there appears to be swallowed - the program keeps running against a half-initialized namespace. In an AOT binary (
cljrs compile) I got a silently wrong answer rather than a crash: the AOT binary printednil,cljrs runon the same source printed0, and the correct answer was183850. The dispatch had quietly fallen through to a:defaultmethod.That second part may deserve its own issue - happy to split it out if you'd prefer. Minimal shape:
cljrs run app.cljc --src-path .→0;cljrs compile app.cljc -o app --src-path .then./app→nil; JVM Clojure →183850.Possibly related, possibly separate
deftypefails earlier - the positional constructor does not seem to be generated at all:Environment
clojurust@c02b399e(main)cargo build -p cljrs, debug profilecargo update kstring --precise 2.0.2, sincekstring 2.0.4requires rustc 1.96)Verified working alongside the above, for contrast:
binding+ dynamic vars,defmulti/defmethod,reduce/map/range, keyword field access,extend-type.