Skip to content

bug: inline defrecord protocol-method bodies cannot resolve the record's own fields #292

Description

@BuddhiLW

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 ./appnil; 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions