Skip to content

Fix cross-namespace defmethod: Unknown symbol on compile#377

Open
dankinsoid wants to merge 1 commit into
Tensegritics:mainfrom
dankinsoid:fix/cross-ns-defmethod
Open

Fix cross-namespace defmethod: Unknown symbol on compile#377
dankinsoid wants to merge 1 commit into
Tensegritics:mainfrom
dankinsoid:fix/cross-ns-defmethod

Conversation

@dankinsoid

Copy link
Copy Markdown

Bug

Any defmethod extending a multimethod defmulti'd in a different namespace fails to compile:

;; repro/multi.cljc
(ns repro.multi)
(defmulti kind :type)
(defmethod kind :same-ns [_] "same-ns")

;; repro/ext.cljc
(ns repro.ext
  (:require [repro.multi :as multi]))
(defmethod multi/kind :cross-ns [_] "cross-ns")
Unknown symbol: multi$SLASH_kind1jgmz0t (no source location)

Reproduced on current master (178e4fb) with a fresh (uncached) compile of a plain :kind :dart project. Note that a warm .clojuredart cache can mask the failure, which may be why this survives day-to-day incremental use.

Root cause

expand-method-impl rebuilds the dispatch table from (keys contributions) — the bare synthetic method-class names. emit-contribute* keys contributions by that bare name and stores the relocatable ($lib:-qualified, via relocatable-class-name) name as the value, keyed by contributing namespace:

contributions ; {bare-cname {contributing-ns relocatable-cname}}

A bare name only resolves while compiling the namespace that defined the method class. For a same-namespace defmethod that happens to be the multimethod's own namespace, so it works; for a cross-namespace one, the multimethod's namespace can't resolve it. The emitted table makes this visible — the same-ns entry is resolved, the cross-ns one isn't:

(-> {} transient ((kind1r4nwxi)) ((multi$SLASH_kind1jgmz0t)) persistent!)

Protocol extensions already handle this correctly by reading the relocatable names out of the contribution values ((vals exts) in the extensions expansion).

Fix

Build the table from the contribution values instead:

~@(for [cname (mapcat vals (vals contributions))] ...)

After the fix the repro compiles and both dispatches work at runtime (same-ns / cross-ns both print) — same-namespace methods keep working because relocatable names resolve locally too, same as for protocols.

…contribution names

expand-method-impl rebuilt the multimethod dispatch table from
(keys contributions) — the bare synthetic class names. A bare name only
resolves when the defmethod lives in the same namespace as its defmulti;
for a cross-namespace defmethod, compiling the multimethod's namespace
fails with:

    Unknown symbol: multi$SLASH_kind... (no source location)

emit-contribute* already stores the relocatable ($lib:-qualified) class
name as the contribution value (relocatable-class-name), keyed by
contributing namespace — the same resolution mechanism protocol
extensions use. Build the table from those values instead.

Minimal repro (fails before, compiles and dispatches correctly after —
same-namespace methods keep working, their relocatable names resolve
locally too):

    ;; repro/multi.cljc
    (ns repro.multi)
    (defmulti kind :type)
    (defmethod kind :same-ns [_] "same-ns")

    ;; repro/ext.cljc
    (ns repro.ext (:require [repro.multi :as multi]))
    (defmethod multi/kind :cross-ns [_] "cross-ns")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant