Cannot use class from another namespace in ns :gen-class

879 views Asked by At

I have a defrecord called ConstraintLookup in sre.plan.dsl.constraint namespace. I want to use its generated class in a gen-class method placed on the sre.plan.compiler namespace:

(ns sre.plan.compiler
  (:require
    [sre.plan.dsl.constraint :as constraint])
  (:import (sre.plan.dsl.constraint ConstraintLookup))
  (:gen-class
    :name sre.plan.Compiler
    :methods [^:static [makeConstraintLookupFromTargetsAndBounds 
                         [Iterable Iterable] ConstraintLookup]]))

I am AOT compiling with nebula-clojure plugin and Gradle. The compiler emits an error when it encounters the ns declaration:

> Task :sre:compileClojure
Exception in thread "main" java.lang.ClassNotFoundException: java.lang.ConstraintLookup, compiling:(sre/plan/compiler.clj:1:1)

Similarly when using fully qualified sre.plan.dsl.constraint.Constraint in the method declaration I get:

Exception in thread "main" java.lang.ClassNotFoundException: sre.plan.dsl.constraint.ConstraintLookup, compiling:(sre/plan/compiler.clj:1:1)

What is the problem here? I am lost.

UPDATE:

The referenced ns looks like this:

(ns sre.plan.dsl.constraint
  (:require [clojure.set :refer :all]
            [clojure.algo.generic.functor :refer :all]))

(defrecord ConstraintLookup [free bound])

UPDATE:

It seems to me that in gen-class you have to use fully qualified class names no matter what. However I still don't understand why doesn't the version with the fully qualified name work.

1

There are 1 answers

0
ez121sl On BEST ANSWER

There is a good chance the :gen-class directive inside the ns macro cannot refer to classes generated as a side effect of :require in the same form. The code emitted by the ns macro calls gen-class before calling any of the requires. Thus the required namespaces are not yet compiled when gen-class is called. gen-class is called before any classes from defrecord are generated.

The behavior of ns can be seen in the source code and also in the repl using macroexpand:

(clojure.pprint/pprint (macroexpand '(ns sre.plan.compiler
  (:require
    [sre.plan.dsl.constraint :as constraint])
  (:import (sre.plan.dsl.constraint ConstraintLookup))
  (:gen-class
    :name sre.plan.Compiler
    :methods [^:static [makeConstraintLookupFromTargetsAndBounds 
                         [Iterable Iterable] ConstraintLookup]]))))
;; (do
;;  (clojure.core/in-ns 'sre.plan.compiler)
;;  (clojure.core/with-loading-context
;;    (clojure.core/gen-class
;;     :name
;;     "sre.plan.compiler"
;;     :impl-ns
;;     sre.plan.compiler
;;     :main
;;     true
;;     :name
;;     sre.plan.Compiler
;;     :methods
;;     [[makeConstraintLookupFromTargetsAndBounds
;;       [Iterable Iterable]
;;       ConstraintLookup]])
;;    (clojure.core/refer 'clojure.core)
;;    (clojure.core/require '[sre.plan.dsl.constraint :as constraint])
;;    (clojure.core/import '(sre.plan.dsl.constraint ConstraintLookup)))
;;   (if
;;    (.equals 'sre.plan.compiler 'clojure.core)
;;    nil
;;    (do
;;     (clojure.core/dosync
;;      (clojure.core/commute
;;       @#'clojure.core/*loaded-libs*
;;       clojure.core/conj
;;       'sre.plan.compiler))
;;     nil)))

To work around the problem, we can call gen-class after ns. For example:

(ns sre.plan.compiler
  (:require
    [sre.plan.dsl.constraint :as constraint])
  (:import (sre.plan.dsl.constraint ConstraintLookup)))

(gen-class
 :impl-ns
 sre.plan.compiler
 :main
 true
 :name
 sre.plan.Compiler
 :methods
 [[makeConstraintLookupFromTargetsAndBounds
   [Iterable Iterable]
   sre.plan.dsl.constraint.ConstraintLookup]])