Permalink
Browse files

:require matching ns when importing classes created by deftype

Classes created by deftype and defrecord are created on ns evaluation,
therefore that ns must be required before referencing these classes.

deftype and defrecord classes implement IType and IRecord in Clojure
1.3.0+, so we can check the hierarchy to identify them.

There is unfortunately no straightforward mapping of class package names
to Clojure namespaces as deftype uses clojure.core/namespace-munge to
simply change all \- to \_.

Therefore a two step search for the matching ns is made:

    1. Try the simple case where all underscores are converted to dashes
    2. Search for the first namespace that matches the package name with
       dashes and/or underscores.

Closes #64
  • Loading branch information...
1 parent 197e41b commit ebdb8311b015f03366cf4a6b7c24f9098bc5ab3b @guns guns committed Jan 29, 2014
Showing with 40 additions and 3 deletions.
  1. +37 −2 src/slam/hound/regrow.clj
  2. +2 −1 test/slam/hound/regrow_test.clj
  3. +1 −0 test/slam/hound_test.clj
@@ -252,15 +252,50 @@
[:refer-all c]
[type c]))))
+(defn- deftype? [cls]
+ (or (.isAssignableFrom clojure.lang.IType cls)
+ (.isAssignableFrom clojure.lang.IRecord cls)))
+
+(defn- make-munged-ns-pattern [package-name]
+ (->> (string/split package-name #"_")
+ (map #(Pattern/quote %))
+ (string/join "[_-]")
+ (#(Pattern/compile (str "\\A" % "\\z")))))
+
+(defn- find-matching-ns
+ "Returns a ns symbol or nil"
+ [package-name]
+ ;; Try the simple case before doing a search
+ (let [ns-sym (symbol (string/replace package-name \_ \-))]
+ (if (find-ns ns-sym)
+ ns-sym
+ (let [pat (make-munged-ns-pattern package-name)]
+ (first (filter #(re-find pat (str (ns-name %))) (all-ns)))))))
+
+(defn- update-imports-in
+ "Adds candidate to :import entry in ns-map, and also adds matching namespace
+ to :require if the candidate class was created by deftype or defrecord."
+ [ns-map candidate]
+ (let [class-name (str candidate)
+ cls (Class/forName class-name)
+ ns-map (update-in ns-map [:import] #(conj (or % #{}) candidate))]
+ (if (deftype? cls)
+ ;; cf. slam.hound.stitch/get-package
+ (let [package-name (second (re-find #"(.*)\." class-name))
+ ns-sym (find-matching-ns package-name)]
+ (cond->* ns-map
+ ns-sym (update-in [:require] #(conj (or % #{}) ns-sym))))
+ ns-map)))
+
(defn grow-ns-map
- "Return a new ns-map augmented with a single candidate ns reference."
+ "Return a new ns-map augmented with candidate ns reference(s)."
[ns-map type missing body]
(let [cs (candidates type missing body)
old-ns-map (:old ns-map)]
(if-let [[type c] (disambiguate cs type missing {:old-ns-map old-ns-map
:new-ns-map ns-map})]
(case type
- :import (update-in ns-map [:import] #(conj (or % #{}) c))
+ :import (update-imports-in ns-map c)
:alias (update-in ns-map [:alias] assoc c missing)
:refer (update-in ns-map [:refer c] #(conj (or % #{}) missing))
:refer-all (update-in ns-map [:refer-all] #(conj (or % #{}) c)))
@@ -128,7 +128,8 @@
(deftest ^:unit test-grow-ns-map
(testing "finds basic imports, aliases, and refers"
(is (= (grow-ns-map {} :import 'RegrowTestRecord '((RegrowTestRecord.)))
- '{:import #{slam.hound.regrow_test.RegrowTestRecord}}))
+ '{:import #{slam.hound.regrow_test.RegrowTestRecord}
+ :require #{slam.hound.regrow-test}}))
(is (= (grow-ns-map {} :alias 'string '((string/join)))
'{:alias {clojure.string string}}))
(is (= (grow-ns-map {} :refer 'pprint '((pprint [])))
@@ -62,6 +62,7 @@
(:require [clojure.java.io :as io]
[clojure.set :as set]
[clojure.test :refer [deftest is]]
+ [slam.hound-test]
[slam.hound.stitch :refer [ns-from-map]])
(:import (clojure.lang Compiler$BodyExpr)
(java.io ByteArrayInputStream File)

0 comments on commit ebdb831

Please sign in to comment.