What causes "Error: No protocol method XXX.YYY defined for type undefined" in ClojureScript but not Clojure?

Viewed 1048

I have been getting errors like the following:

#object[Error Error: No protocol method XXX.YYY defined for type undefined: ]

where the XXX.YYY part is variable. This code is in a *.cljc file and runs fine in JVM Clojure but fails in ClojureScript. What could be the cause?

1 Answers

This obscure error message can be caused by inadvertent references to JVM classes that have not been properly guarded with #?(:clj ...) and #?(:cljs ...) reader conditionals. For the above example, the problematic code was this:

(ns tupelo.schema
  "Prismatic Schema type definitions"
  (:require [schema.core :as s])
  #?(:clj (:import [java.util HashSet] ))
  #?(:clj (:gen-class)))

(def Set
  "Either a Clojure hash-set or a java.util.HashSet"
  (s/either #{s/Any}
    java.util.HashSet))

the correct version looks like so:

(def Set
  "Either a Clojure hash-set or a java.util.HashSet"
  (s/either #{s/Any}
    #?(:clj java.util.HashSet)))   ; <= must guard the java class reference

These errors are particularly insidious since the error message is so vague, and there was no reference to the offending file & line. In fact, in this case it was caused by a chain of references across 4 files:

tst.tupelo.core -> tupelo.core -> tupelo.impl -> tupelo.schema

For reference, the following is an example of how to successfully write dual-purpose CLJ & CLJS code:

(is (instance?
      #?(:clj  clojure.lang.PersistentVector)
      #?(:cljs    cljs.core/PersistentVector)
      [1 2 3]))

So you can see there are usually equivalents in both CLJ & CLJS, but the names are different enough that you must correctly use the reader conditionals #?(:clj ...) and #?(:cljs ...). Otherwise, your code will fail with a vague error message.

Related